频道栏目
读书频道 > web开发 > php > PHP框架高级编程——应用Symfony、CakePHP和Zend
1.3.3 其他设计模式概述
2013-01-04 13:46:03     我来说两句
收藏   我要投稿
尽管学习如何使用单个PHP框架生成动态网页相对简单,但确定这3种主流Web应用程序框架中哪一个最能满足您的要求却并不容易。《PHP框架高级编程——应用Symfony、CakePHP和Zend》一书对3种最受欢迎的开源框架进行了...  立即去当当网订购

设计模式可以划分为创造模式、行为模式和结构模式3种。完整描述所有这些设计模式超出了本书的范围,在下面这本非常有影响力的书中可以找到相应描述:Design Patterns: Elements of Reusable Object-Oriented Software(作者:Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides)。下面简要介绍Web框架中常用的一些设计模式。

1. Singleton

这种设计模式通常称为反模式,它很普通但非常有用。Singleton模式的目的是确保类只有一个实例,并且让这个实例在全局范围内可访问。当另一个对象需要访问Singleton模式时,它将调用一个全局可访问的静态函数来返回对单个实例的引用。图1-19显示的是Singleton模式的结构。


 

Singleton模式的诀窍是让该实例及其所有构造函数私有化,从而无法创建Singleton类的实例。那么如何创建第一个也是唯一一个实例呢?首先,instance()方法检查对象是否存在;如果不存在,那么在返回它之前创建一个实例。下面看看使用PHP代码如何实现:
<?php
class CarSingleton {
    private $make = 'Dodge';
    private $model = 'Magnum';
    private static $car = NULL;
    private static $isRented = FALSE;
    private function __construct() {
    }
    static function rentCar() {
        if (FALSE == self::$isRented ) {
            if (NULL == self::$car) {
                self::$car= new CarSingleton();
            }
            self::$isRented = TRUE;
            return self::$car;
        } else {
            return NULL;
        }
    }
    function returnCar(CarSingleton $carReturned) {
        self::$isRented = FALSE;
    }
    function getMake() {return $this->make;}
    function getModel() {return $this->model;}
    function getMakeAndModel() {return $this->getMake().' '.$this->getModel();}
}
?>
codesnippet/singleton/CarSingleton.class.php

上面代码中的类是一个Singleton模式,表示汽车租赁公司Dodge Magnum的一个具体的汽车样本。__construct()函数是这个类的构造函数。请注意,将它设置为private以防止从类外部使用它。双下划线表示__construct()是PHP中的魔法函数(该语言提供的特殊函数)之一,在类中声明构造函数将重写默认的构造函数。

CarSingleton提供一个界面,表示租车和还车以及租车者。rentCar()函数首先检查汽车是否已经租出。这不是Singleton模式的一部分,但对于本示例的逻辑非常重要。如果汽车没有租出,那么该函数在返回之前先检查$car变量是否为NULL。如果该变量等于NULL,那么在首次使用之前先构建它。因此,rentCar()函数对应设计模式的instance()方法。

下面示例中的Customer类表示享受汽车租赁公司服务的某个人。他可以租车(这里只有一辆车)、还车、了解正在驾驶的汽车的制造商和型号。
<?php
include_once('CarSingleton.class.php');
class Customer{
    private $rentedCar;
    private $drivesCar = FALSE;
    function __construct() {
    }
    function rentCar() {
        $this->rentedCar = CarSingleton::rentCar();
        if ($this->rentedCar == NULL) {
            $this->drivesCar = FALSE;
        } else {
            $this->drivesCar = TRUE;
        }
    }
    function returnCar() {
        $this->rentedCar->returnCar($this->rentedCar);
    }
    function getMakeAndModel() {
        if (TRUE == $this->drivesCar ) {
            return 'I drive '.$this->rentedCar->getMakeAndModel().' really
fast!';
        } else {
            return "I can't rent this car.";
        }
    }
}
?>
codesnippet/singleton/Customer.class.php

可以用下面的代码来测试这些类。该代码创建两个客户,他们同时要租车。但第二个客户只有等到第一个客户还车后才能租到车。
<?php
include_once('Customer.class.php');
$Customer_1 = new Customer();
$Customer_2 = new Customer();
echo 'Customer_1 wants to rent the car. <br />';
$Customer_1->rentCar();
echo 'Customer_1 says: ' . $Customer_1->getMakeAndModel() . '<br />';
echo '<br />';
echo 'Customer_2 wants to rent the car. <br />';
$Customer_2->rentCar();
echo 'Customer_2 says: ' . $Customer_2->getMakeAndModel() . '<br />';
echo '<br />';
$Customer_1->returnCar();
echo 'Customer_1 returned the car.<br />';
echo '<br />';
echo 'Customer_2 wants to rent the car. Again.' . '<br />';
$Customer_2->rentCar();
echo 'Customer_2 says: ' . $Customer_2->getMakeAndModel() . '<br />';
echo '<br />';
?>
codesnippet/singleton/Test.php

输出如下所示:
Customer_1 wants to rent the car.
Customer_1 says: I drive Dodge Magnum really fast!

Customer_2 wants to rent the car.
Customer_2 says: I can't rent this car.

Customer_1 returned the car.

Customer_2 wants to rent the car. Again.
Customer_2 says: I drive Dodge Magnum really fast!

Singleton模式通常用于其他设计模式中,例如Prototype、State、Abstract Factory或Facade。除此之外,还可以在所有需要全局访问的单个实例的类中使用它,但无法将它指派给其他对象,也许还可以从首次使用时的初始化中受益。请注意,很容易过度使用Singletons模式,这样很危险,就像过度使用全局变量一样。使用Singletons模式的另一个问题是在程序的整个执行过程中它们都保持状态不变,这样会严重影响单元测试。有些专家甚至说Singleton模式不是一个好模式,应该避免使用它。

但框架使用Singleton模式有多种原因。原因之一是出于安全的目的存储用户数据。需要一个实例来保存用户验证数据,并且确保不能创建其他实例。例如,Symfony的sfGuard类就使用这种方法。

2. Prototype

当需要灵活创建参数化对象但又不想使用new操作符时,就可以使用Prototype模式。使用抽象的clone()方法和一些实现clone()的子类创建一个父类,这样就可以创建对象。每个子类都包含一个实例化的Prototype对象,调用新实例时,它复制本身。这样就很容易灵活地创建对象—— 不需要在代码中对具体子类名称进行硬连接,而且可以作为字符串或者对相应Prototype的引用来传递类的名称。

这种模式还非常支持对象的深度复制。这种复制不是复制Prototype,而是复制现有对象,然后接收该对象的副本作为结果。甚至还可以从包含各种子类的混合对象的容器中复制对象。唯一要求是它们要实现clone()接口。用这种方法复制对象比使用new操作符并指定值来创建对象更快。这种模式的通用示意图如图1-20所示。


 

PHP还有另一个魔法函数:__clone()能够完成大多数任务。在下面的示例中,唯一要做的就是为不同生产者创建一个抽象的CarPrototype类和子类。__clone()函数声明为abstract类型,因此当调用这种方法时,默认使用子类方法。
<?php
abstract class CarPrototype {
    protected $model;
    protected $color;
    abstract function __clone();
    function getModel() {
        return $this->model;
    }
    function getColor() {
        return $this->color;
    }
    function setColor($colorIn) {
        $this->color= $colorIn;
    }
}
class DodgeCarPrototype extends CarPrototype {
    function __construct() {
        $this->model = 'Dodge Magnum';
    }
    function __clone() {
    }
}
class SubaruCarPrototype extends CarPrototype {
    function __construct() {
        $this->model = 'Subaru Outback';
    }
    function __clone() {
    }
}
?>
codesnippet/prototype/CarPrototype.class.php

汽车就是非常好的示例,因为在现实中制造商创建原型,然后基于这种原型创建不同的模型,再填充特有功能。下面的代码用于测试前面的类。首先,它创建两个Prototype对象作为展示的汽车,然后复制其中一个对象来满足客户,并可通过统一的界面来挑选颜色。
<?php
include_once('CarPrototype.class.php');
$dodgeProto= new DodgeCarPrototype();
$subaruProto = new SubaruCarPrototype();
echo 'Which car do you want? <br />';
$customerDecision = 'Subaru';
if( $customerDecision == 'Subaru' ){
   $customersCar = clone $subaruProto;
} else {
   $customersCar = clone $dodgeProto;
}
echo $customersCar->getModel().'<br />';
echo 'What color do you want?<br />';
$customersCar->setColor('red');
echo 'Fine, we will paint your '.$customersCar->getModel().
     '  '.$customersCar->getColor().'.<br />';
?>
codesnippet/prototype/Test.php

前面的代码将得到下面的消息:
Which car do you want?
Subaru Outback.
What color do you want?
Fine, we will paint your Subaru Outback red.

通常在框架的不同模块中使用Prototype模式。例如,可以在Symfony或者CakePHP的AppController类的表单中嵌套表单。

3. Decorator

子类化是一个很好的机制,但它也有一些严重的局限性。假设要生产一辆汽车,你努力设计一种别人能买得起的汽车标准模型。这种完整的设计定义模型的观感,并且是以后所有修改的参考。然后你可以提供一些可选配置,这些配置会给汽车增加一些新功能,从而提高汽车的性能。例如,它可能是四轮驱动而不是前轮驱动,可能是自动档而不是手动档。该车可能有不同的装配级别,可能包含电动皮革座椅、天窗、更好的音频系统或者GPS卫星导航系统。但基本界面相同—— 你可以开这辆车,并且感觉良好。

如果面临这些选择,就会大大增加可能的组合形式。图1-21显示3种改进的一些组合,如继承层次结构所示。


 

解决这个问题的答案是Decorator模式。Decorator这个类与装饰类共享接口(在本示例中,它是包含基本配置的汽车)。它封装装饰对象的实例,并动态扩展其性能。这就像把礼物放在一个盒子里,然后用彩纸将它包装起来一样—— 虽然它仍然是一份礼物,但更耐磨、更漂亮。Decorator模式的继承结构如图1-22所示。


 

可以毫无限制地将装饰对象放入其他Decorator模式中。用这种方法可以随意添加多个可选模块。Decorator模式也有自身的继承层次结构,在层次结构中它们能够递归封装核心对象。

下面的代码创建一个没有可选设备的标准Car类。
<?php
class Car{
  public $gearMessage = 'Remember to shift up.';
  public $comfortMessage = 'standard.';
  function drive() {
    return 'Accelerating ' . $this->gearMessage .
           ' Driving comfort is ' . $this->comfortMessage;
  }
}
?>
codesnippet/decorator/Car.class.php

下面的类负责扩展汽车的性能。第一个类CarDecorator是包装的第一层。它存储$car变量和$comfortMessage变量的副本。Decorator模式会改变这个变量,因此创建一个副本以免改变原始的$car对象。另一方面, $gearMessage也在内部改变。子类化drive()函数以使用合适的变量$car->model和$this->gearMessage,应该访问核心对象而不是$this->comfortMessage,因为要用到修改后的值。

包装CarDecorator的第二层装饰类用来安装可选组件,如下所示。Automatic TransmissionDecorator将$gearMessage直接安装到核心的$car中,而GPSDecorator安装到CarDecorator中。请注意,所有装饰类都共享相同接口,另外还提供具体的安装程序。
<?php
    class CarDecorator {
      protected $car;
      protected $gearMessage;
      protected $comfortMessage ;
      public function __construct(Car $car_in) {
        $this->car = $car_in;
        $this->comfortMessage = $car_in->comfortMessage;
      }
      function drive() {
        return 'Accelerating. ' . $this->car->gearMessage .
               ' Driving comfort is ' . $this->comfortMessage;
      }
    }

    class AutomaticTransmissionDecorator extends CarDecorator {
      protected $decorator;
      public function __construct(CarDecorator $decorator_in) {
        $this->decorator= $decorator_in;
      }
      public function installAutomaticTransmission(){
        $this->decorator->car->gearMessage = 'Auto transmission shifts
up.';
      }
    }
    class GPSDecorator extends CarDecorator {
      protected $decorator;
      public function __construct(CarDecorator $decorator_in) {
        $this->decorator= $decorator_in;
      }
      public function installGPS(){
        $this->decorator->comfortMessage= 'very high.';
      }
    }
?>
codesnippet/decorator/CarDecorator.class.php

使用下面的代码来测试这些类。
<?php
  include_once('Car.class.php');
  include_once('CarDecorator.class.php');
  $car = new Car();
  $decorator = new CarDecorator($car);
  $transmission = new AutomaticTransmissionDecorator($decorator);
  $gps = new GPSDecorator($decorator);
  echo 'Driving standard car: <br />';
  echo $car->drive().'<br />';
  $transmission->installAutomaticTransmission();
  $gps->installGPS();
  echo 'Driving fully decorated car: <br />';
  echo $decorator->drive() . '<br />';
  echo 'Driving the car without decoration: <br />';
  echo $car->drive() . '<br />';
?>
codesnippet/decorator/Test.php

结果如下所示:
Driving standard car:
Accelerating. Remember to shift up. Driving comfort is standard.
Driving fully decorated car:
Accelerating. Auto transmission shifts up. Driving comfort is very high.
Driving the car without decoration:
Accelerating. Auto transmission shifts up. Driving comfort is standard.

首先调用基本的car模型。接着安装可选配置,调用CarDecorator的drive()函数。最后,选择开车,而不使用Decorator包装。请注意,调用$car之后,选择的是自动档,因为Decorator模式永久性地改变了它。

接着讨论框架,Decorator模式也用于其他布局和模板中。当需要添加可选视觉组件或者需要新微件来扩展用户界面时,这种模式非常有用。例如,如果用户输入超过字段区域,就会添加滚动条。

4. Chain of Responsibility

前面的3种设计模式关注的是对象创建和继承结构。Chain of Responsibility是另一种类型的模式,因为它应用于对象的行为。它的主要目的是分离请求的发送方与接收方。下面用汽车示例来说明其用法。

假设道路出现紧急情况,需要马上停车。换句话说,发出了stop请求。在大多数情况下,踩下刹车踏板就可以了,但有时制动器会失灵;这时Chain of Responsibility就派上用场了。如果制动器不能处理该请求,就会将它传递给手动制动器。如果由于某种原因手动制动器也失灵,那么撞上障碍物之后,至少安全气囊应该弹出以保住乘客的性命。对于大多数道路紧急事件而言,安全气囊是最常见的解决方案。虽然与专业解决方案(刹车、避让)相比不是最好的,但如果这些解决方案都无效,安全气囊解决方案就显得尤为重要。这与应用程序是一样的—— 最好为请求提供一个潜在的处理程序链(如图1-23所示),而不是在它无效的时候连一条错误消息都没有。


 

那么该如何创建这种Chain of Responsibility呢?这种模式的主要思想就是通过一系列连续的处理程序来处理请求,以避免硬件连接的映射。在处理程序链中,最初客户只保持对第一个处理程序的引用,每个处理程序保持对下一个处理程序的引用。最后一个处理程序必须总是接受请求,以避免将它传递给NULL值。

支持这种行为模式的类结构如图1-24所示。它由一个父Handler类组成,该父类通过调用handle()方法将请求委托给下一个具体的处理程序nextHandler。具体的处理程序子类化Handler类,这些具体的处理程序会处理请求;如果它们处理失败,则调用它们超类的handle()方法。


 

Chain of Responsibility通常用于过滤器,处理用户请求就是其中一个过滤的示例。首先检查给定的控制器是否存在。如果不存在,则显示404 error。如果存在,则将请求传递给控制器,它进一步处理请求。它检查用户是否试图访问不安全的页面;如果是,那么它将请求重定向到一个安全的SSL页面,然后检查身份验证等。

5. State

有时我们希望组件根据应用程序的各种可能状态做出不同响应。首先定义一个抽象的State类,它是各种ConcreteStates的通用接口。所有状态都提供一种handle()方法,这种方法提供组件的各种行为。Context类是包装ConcreteState state对象的核心类。如果Context类是提供状态独立功能的完整类,这种设计模式就非常有用。否则,子类化Context类可能更有效。

在处理它自己的请求时,Context类调用state->handle()方法。Context类还有一些方法能够切换状态。依据state变量存放的ConcreteState不同,state->handle()方法提供不同的行为。可以将它用于模拟运行时部分类型的改变。其示意图如图1-25所示。


 

State模式虽然很简单,但对于应用程序开发来说却非常有用,例如数据库连接—— 数据库抽象层可能依据当前的连接状态改变行为。再例如联机商店的交易状态,应用程序可以根据需要哪些步骤完成交易来显示不同的页面。

6. Iterator

有许多类型的集合对象,也有许多方法能够遍历它们。例如,由连续整数遍历的数组可应用于数组运算符。如果要打印出包含5个元素的myArray,那么可以使用下面的代码:
for ($i=0;$i<=4;$i++) {
echo $myArray[$i];
}

但是这种解决方案并不完美。首先必须确定变量i的值。PHP不是C/C++,因此调用myArray[100]并不是灾难性的—— 它不会从内存返回随机垃圾数据。然而它仍然很容易跳过某些硬连接范围内的值。另一个问题是,这种方法显示了聚集的基本表示,它使得这种遍历过程依赖具体的表示,因此不可重用。面向对象编程的目的就是封装聚集对象的内部结构,并且只提供一个统一、安全的有用接口,就像PHP提供的接口一样:
interface Iterator{
function current(); // Returns the value of element under current key
function key(); // Returns the current key
function next(); // Moves the internal pointer to the next element
function rewind(); // Moves the internal pointer to the first element
function valid(); // Returns true if the element under current key is valid
}

现在每个实现这种接口的类都可以使用foreach结构。下面的代码片段产生的输出与前面的for循环一样:
foreach ($myArray as $value) {
echo $value;
}

这种机制背后的抽象化内容是Iterator设计模式,如图1-26所示。Client应用程序能够访问两个抽象类:Collection和TraversalAbstraction,前者是聚集对象接口,后者是由相应的Collection创建的。对于List和Map而言,具体聚集可能不同,但可以为它们生成相应的遍历方法。Client调用next()方法,为List和Map执行不同的排序算法,但在这两种情况下,将会出现并发元素。


 

在Web框架中,Iterator模式主要用于分页。需要一个统一界面将Web内容划分到足够多的页面中,再将它们转换到单独网页中,然后通过这些页面遍历内容。

您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:1.3.2 模型-视图-控制器作为主要的结构设计模式
下一篇:概述
相关文章
图文推荐
排行
热门
最新书评
文章
下载
读书
特别推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站