深入PHP设计模式

一、什么是设计模式?为什么要使用它


    一个设计模式的核心由4部分组成:命名、问题、解决方案和效果。


    1、命名
       命名非常重要。命名丰富了程序员的语言,少许简短的文字便可以表示相对复杂的问题和解决方案。命名必须兼顾简洁性和描述性。《设计模式》说道:找到一个好名字,成了我们在开发模式目录中最困难的部分之一。
    2、解决方案
       解决方案最初是和问题放在一起的,并用UML类图和交互图来更详细地进行描述。而模式通常也包含一个代码范例。
        尽管代码也许是现成的,但解决方案从来不是简单的剪切及黏贴。模式描述了一个问题的一个解决方法。


二、模式原则
    1、组合与继承
        继承是应对变化的环境及上下文设计的有效方式。然而他会限制灵活性,尤其当类承担多重责任的时候。


    2、使用组合
        使用策略(Strategy)模式,策略模式适用于将一组算法移入到一个独立的类型中。通过移走费用计算相关的代码,可以简化Lesson类型。
        我们要创建一个名为CostStrategy的抽象类,它定义了抽象方法cost()和chargeType()。cost()方法需要一个Lesson实例,用于生产费用数据。
    <?php
        abstract class Lesson{


        private $duration;
        private $costStrategy;//策略模型


        function __construct($duration,costStrategy $strategy){
            $this->duration = $duration;
            $this->costStrategy = $strategy;
        }


        function cost(){
            return $this->costStrategy->cost($this);
        }


        function chargeType(){
            return $this->costStrategy->chargeType();
        }


        function getDuration(){
            return $this->duration;
        }
    
    }


    //Seminar课
    class Seminar extends Lesson{
        
    }


    class Lecture extends Lesson{
        
    }






    //计算费用类
    abstract class CostStrategy{
        abstract function cost(Lesson $lesson);
        abstract function chargeType();
    }


    //按小时计算的课
    class TimedCostStrategy extends CostStrategy{
        function cost(Lesson $lesson){
            return ($lesson->getDuration() * 5);
        }


        function chargeType(){
            return "hourly rate";
        }
    }




    //固定费用
    class FixedCostStrategy extends CostStrategy{


        function cost(Lesson $lesson){
            return 30;
        }


        function chargeType(){
            return "fixed rate";
        }
    }


    $lessons[] = new Seminar(4, new TimedCostStrategy() );


    $lessons[] = new Lecture(4,new FixedCostStrategy() );


    foreach($lessons as $lesson){
        print "lesson charge {$lesson->cost()}. ";
        print "Charge type:{$lesson->chargeType()}\n";
    }
    ?>
    此结构效果之一就是让我们关注类的职责。CostStrategy对象独立负责计算费用,而Lesson对象则负责管理课程数据。
    所以,组合使用对象比使用继承体系更灵活,因为组合可以以多种方式动态地处理任务。尽管这可能导致代码的可读性下降。由于组合需要更多的对象类型,而这些类型的关系并不像在继承关系中那般有固定的可预见性,所以要理解系统中类和对象的关系会有些困难。


三、解耦
    创建独立的组件是很有意义的,但如果类之间有非常强的依赖性,那么这样的系统就很难维护。因为系统里的一个改动会引起一连串的相关改动。
    重用性是面向对象设计的主要目标之一,而紧耦合就是它的敌人。当我们看到系统中一个组件的改变迫使系统其它许多地方也发生改变的时候,就可以诊断为紧耦合了。
    不过当系统总许多类都显式子嵌入到一个平台或环境中时,其他类型的耦合依然会有。比如你建立了一个基于MySQL数据库的系统。你可能会用一些诸如mysql_connect()和mysql_query()的函数来与数据库服务器交互。
    如果现在你被要求在不支持MySQL的服务器商部署系统,比如要把整个项目都转换成使用SQLite,那么你可能被迫要改变整个代码,并且面临维护应用程序的两个并行版本。
    这里的问题不在于系统对外部平台的依赖。这样的依赖是无法避免的。我们确实需要使用与数据库交互的代码。但当这样的代码散布在整个项目中时,问题就来了。与数据库交互不是系统中大部分类的首要责任,因此最好的策略是提取这样的代码并将其组合在公共接口后。这可以使类之间相互独立。同时,通过在一个地方集中你的“入口代码”,就能更轻松地切换到一个新的平台而不会影响到系统中更大的部分。
    3、1 降低耦合
         为了灵活处理数据库代码,我们应该讲应用逻辑从数据库平台的特殊性中解耦出来。我们一般使用PDO扩展或PEAR:MDB2包。
         下面的代码使用PEAT:MDB2包来访问一个MySQL数据库:
         require_once "MDB2.php";
         $dsn = "mysql://mattz@localhost/test";


         $mdb2 = MDB2::connect($dsn);
         $query_result = $mdb2->query("SELECT * FROM bobs_table");
         while($row = $query_result->fetchRow()){
         
         }
         $mdb2->disconnect();
         MDB2类提供了一个静态方法connect(),他接受一个数据源名(DSN,Data
         SourceName)字符串参数。根据这个字符串的构成,它返回MDB2_Driver_Common类的一个特定实现。因此对于字符串"mysql://",connect()方法返回一个MDB2_Driver_mysql对象,而对于一个以"sqlite://"开头的字符串,它将返回一个MDB2_Driver_sqlite对象。
         使用PEAR::MDB2包,可以从数据库平台的特殊性中将应用程序代码分离出来。你只要使用SQL语句,就可以在MySQL、SQLite和其他数据库上运行一个单一的系统而不需要改变一行代码。




        
四、生成对象
        单例模式:生成一个且只有一个对象实例的特殊类。
        实例单例模式:
          class Preferences{
            private $props = array();
            private static $instance;


            //防止从外部实例化
            private function __constrcut(){}


            public static function getInstance() {
                if( emtpy(self::$instance )){
                    self:$instance = new Preferences;
                }
                return self::$instance;
            }




          }
        


        工厂方法模式:构建创建者类的继承层级。


            面向对象设计强调“抽象类高于实现”。也就是说,我们要尽量一般化而不是特殊化。工厂方法模式解决了当你的代码关注与抽象类型时如何创建对象实例的问题。答案就是用特定的类来处理实例化。
        抽象工厂模式:功能相关产品的创建。
            abstract class CommsManageer {
            
                const APPT = 1;
                const TTD  = 2;
                const CONTACT =3;


                abstract function getHeaderText();


                /*实例化*/
                abstract function make( $flag_int );
                abstract function getFooterText();
            }


            class BloggsCommsManager extends COmmsManager {
            
                function getHeaderText() {
                    return "BloggsCal headdr\n";
                }
    
                public function make( $flag_int ) {
                    switch ( $flag_int ){
                        case self::APPT:
                            return new BloggsApptEncoder();
                        case self::CONTACT:
                            return new BloggsContactEncoder();
                        case self::TTD:
                            return new BloggsTtdEncoder();
                    }
                }


                public function getFooterText() {
                    return "BloggsCal footer/n";
                }
            }
            可以看出类的接口变得更加紧凑。但是这样做也有一定代价。在使用工厂方法时,我们定义了一个清晰的接口并强制所有具体的工厂对象遵循她。而使用单一的make()方法,我们必须在所有的具体创建者中支持所有的产品对象。同时,我们也引入了平行条件,因为每个具体创建者都必须实现相同的编辑检测(flag)。客户端(指调用创建者类的类)无法确定具体的创建者是否可以生产所有产品,因为这是make()方法的内部需要对每种情况都进行考虑并做出选择。
            另一方面,我们可以创建更灵活的创建者。创建者积累可以提供make()方法来保证每个产品家族有一个默认实现。具体子类可以选择性地改变这一行为。子类可以实现自己的make()方法并调用,由此决定生成何种对象。这些创建者子类可以自行决定是否在执行自己的make()方法后调用父类的make()方法。
        原型模式:使用克隆来生成对象。
            平行继承层次的出现时工厂方法模式带来的一个问题。这是一种让一些程序员不舒服的耦合。每次添加产品家族时,你就被迫去创建一个相关的具体创建者(比如编码器BloggsCal和BloggsCommsManager匹配)。在一个快速增长的系统里会包含越来越多的产品,而维护这种关系便会很快令人厌烦。
            一个避免这种依赖的办法是使用PHP的clone关键词复制已存在的具体产品。然后,具体产品本身便成为他们自己生成的基础。这便是原型模式。使用该模式我们可以用组合代替继承。这样的转变则促进了代码运行时的灵活性,并减少了必须促进的类的数量。
            当使用抽象工厂模式或工厂方法模式时,必须决定使用哪个具体的创建者,这很可能是通过检查配置的值来决定的。既然必须这样做,那为什么不简单地创建一个保存具体产品的工厂类,并在初始化时就加入这种做法呢?这样做除了可以减少类的数量,还有其他好处。下面是在工厂中使用原型模式的简单代码:
            class Sea {}
            class EarthSea extends Sea {}
            class MarsSea extends Sea {}


            class Plains {}
            class EarthPlains entends Plains {}
            class MarsPlains extends Plains {}
            class Forest {}
            class EarthForest extends Forest {}
            class MarsForest extends Forest {}
            
            class TerrainFactory {
            
                private $sea;
                private $forest;
                private $plains;


                public function __construct( Sea $sea, Plains $plains, Forest $forest
                ){
                    $this->sea = $sea;
                    $this->plains = $plains;
                    $this->forest = $forest;
                }
    
                public function getSea() {
                
                    return clone $this->sea;
                }


                public function getPlains() {
                    return clone $this->plains;
                }
                
                public function getForeast() {
                    return clone $this->forest;
                }
                
            }


            $factory = new TerrainFactory( new EarthSea(),new
            EarthPlains(),new EarthForest());
            可以看到,我们加载了一个带有产品对象实例的具体的TerrainFactory对象。当客户端代码调用getSea()时,返回一个在初始化时缓存的Sea对象的副本。我们不仅仅节约了一些类,而且有了额外的灵活性。只需要改变添加到TerrainFactory的参数即可。


    4、1 总结
       系统经常根据配置的值来决定选择哪个特定的具体创建者。而这些配置信息可以在数据库、配置文件或服务器文件中。由于PHP应用程序必须为每个请求都重新读取配置文件,所以需要让脚本的初始化尽可能地简单。所以可以选择在PHP代码中通过硬编码来设定配置标记。这可以通过手写或者写一个自动生成类文件的脚本来完成。
       下面是一个包含日历协议类型的标记的类:
       class Settings {
            public static $COMMESTYPE = 'Mega';
       }
       有了这个标记,就能创建一个类,它利用该标记决定用哪个CommsManager来处理请求。这里单例与抽象工厂模式的结合使用是相当常见的。代码如下:
       require_once('Settings.php');


       class AppConfig {
       
           private static $instance;
           private $commsManager;


           private function __construct() {
            //仅运行一次
            $this->init();
           }


           private function init() {
             switch (Settings::$COMMSTYPE ){
                case 'Mega':
                    $this->commsManager = new MegaCommsManager();
                    break;
                default:
                    $this->commsManager = new BloggsCommsManager();
             }
           }


            //实现单例模式
           public static function getInstance() {
            if(empty(self::$instance)){
                self::$instance = new self();
            }


            return self::$instance;
           }


           public function getCommsManager() {
                return $this->commsManager;
           }
       }


       AppConfig类是一个标准的单例。因此,我们可以在系统中任何地方得到AppConfig实例,并且确保得到的一直是同一个实例。init()方法会被类的构造方法调用,且因此在一个进程中之只会运行一次。它检查Settings::$COMMSTYPE属性,并根据该属性的值来实例化一个具体的CommsManager对象。
       $commsMgr = AppConfig::getInstance()->getCommsManager();
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值