当前位置:首页 > PHP教程 > php高级应用 > 列表

PHP设计模式之装饰器(装饰者)模式(Decorator)入门与应用详解

发布:smiling 来源: PHP粉丝网  添加日期:2022-01-28 10:41:39 浏览: 评论:0 

本文实例讲述了PHP设计模式之装饰器(装饰者)模式(Decorator)入门与应用,分享给大家供大家参考,具体如下:

通常情况下,我们如果要给对象添加功能,要么直接修改对象添加相应的功能,要么派生对应的子类来扩展,抑或是使用对象组合的方式,显然,直接修改对应的类这种方式并不可取。

在面向对象的设计中,我们也应该尽量使用对象组合,而不是对象继承来扩展和复用功能,装饰器模式就是基于对象组合的方式,可以很灵活的给对象添加所需要的功能,并且它的本质就是动态组合,一句话,动态是手段,组合才是目的。

也就是说,在这种模式下,我们可以对已有对象的部分内容或者功能进行调整,但是不需要修改原始对象结构,理解了不???

还可以理解为,我们不去修改已有的类,而是通过创建另外一个装饰器类,通过这个装饰器类去动态的扩展其需要修改的内容,而它的好处也是显而易见的,如下:

1、我们可以保证类的层次不会因过多而发生混乱。

2、当我们需求的修改很小时,不用改变原有的数据结构。

我们来看下《PHP设计模式》里面的一个案例:

被修饰类 现在的需求: 要求能够动态为CD添加音轨、能显示CD音轨列表,显示时应采用单行并且为每个音轨都以音轨好为前缀。

  1. class CD { 
  2.   public $trackList; 
  3.   function __construct()  { 
  4.     # code... 
  5.     $this->trackList=array(); 
  6.   } 
  7.   public function addTrack($track){ 
  8.     $this->trackList[]=$track; 
  9.   } 
  10.   public function getTrackList(){ 
  11.     $output=" "; 
  12.     foreach ($this->trackList as $key => $value) { 
  13.       # code... 
  14.       $output.=($key+1).") {$value}. "; 
  15.     } 
  16.     return $output; 
  17.   } 
  18. } 

现在需求发生变化: 要求将当前实例输出的音轨都采用大写形式,这个需求并不是一个变化特别大的需求,不需要修改基类或创建一个父子关系的子类,此时创建一个基于装饰器模式的装饰器类。

  1. class CDTrackListDecoratorCaps{ 
  2.   private $_cd; 
  3.   public function __construct(CD $CD){ 
  4.     $this->_cd=$CD; 
  5.   } 
  6.   public function makeCaps(){ 
  7.     foreach ($this->_cd->trackList as $key => $value) { 
  8.       # code... 
  9.       $this->_cd->trackList[$key]=strtoupper($value); //转换成大写 
  10.     } 
  11.   } 
  12. } 
  13. //客户端测试 
  14. $myCD=new CD(); 
  15. $trackList=array(  "what It Means",  "brr",  "goodBye" ); 
  16. foreach ($trackList as $key => $value) { 
  17.   # code... 
  18.   $myCD->addTrack($value); 
  19. } 
  20. $myCDCaps=new CDTrackListDecoratorCaps($myCD); 
  21. $myCDCaps->makeCaps(); 
  22. print "The CD contains the following tracks:".$myCD->getTrackList(); 

来看一个比较通俗但是比较简单的案例:

设计一个UserInfo类,里面有UserInfo数组,用于存储用户名信息

通过addUser来添加用户名

getUserList方法将打印出用户名信息

现在需要将添加的用户信息变成大写的,我们需要不改变原先的类,并且不改变原先的数据结构

我们设计了一个UserInfoDecorate类来完成这个需求的操作,就像装饰一样,给原先的数据进行了装修

装饰器模式有些像适配器模式,但是一定要注意,装饰器主要是不改变现有对象数据结构的前提

代码如下:

UserInfo.php

装饰器模式,对已有对象的部分内容或者功能进行调整,但是不需要修改原始对象结构,可以使用装饰器设计模式

  1. class UserInfo { 
  2.  public $userInfo = array();  
  3.    
  4.  public function addUser($userInfo) { 
  5.  $this->userInfo[] = $userInfo; 
  6.  } 
  7.    
  8.  public function getUserList() { 
  9.  print_r($this->userInfo); 
  10.  } 
  11. } 

UserInfoDecorate 装饰一样,改变用户信息输出为大写格式,不改变原先UserInfo类

  1. <?php 
  2. include("UserInfo.php"); 
  3. class UserInfoDecorate { 
  4.    
  5.  public function makeCaps($UserInfo) { 
  6.  foreach ($UserInfo->userInfo as &$val) { 
  7.   $val = strtoupper($val); 
  8.  } 
  9.  } 
  10.    
  11. } 
  12. $UserInfo = new UserInfo; 
  13. $UserInfo->addUser('zhu'); 
  14. $UserInfo->addUser('initphp'); 
  15. $UserInfoDecorate = new UserInfoDecorate; 
  16. $UserInfoDecorate->makeCaps($UserInfo); 
  17. $UserInfo->getUserList(); 

到此,咱们应该是对于装饰器模式有了一个大概的了解,接下来咱们看一下构建装饰器模式的案例,网上的,先来看目录结构:

  1. |decorator  #项目根目录 
  2. |--Think  #核心类库 
  3. |----Loder.php  #自动加载类 
  4. |----decorator.php  #装饰器接口 
  5. |----colorDecorator.php  #颜色装饰器 
  6. |----sizeDecorator.php  #字体大小装饰器 
  7. |----echoText.php  #被装饰者 
  8. |--index.php #单一的入口文件 

完事就是来构建装饰器接口,Think/decorator.php,如下:

  1. <?php 
  2. /** 
  3.  * 装饰器接口 
  4.  * Interface decorator 
  5.  * @package Think 
  6.  */ 
  7. namespace Think; 
  8. interface decorator{ 
  9.   public function beforeDraw(); 
  10.   public function afterDraw(); 
  11. } 

再来就是颜色装饰器 Think/colorDecorator.php,如下:

  1. <?php 
  2. /** 
  3.  * 颜色装饰器 
  4.  */ 
  5. namespace Think; 
  6. class colorDecorator implements decorator{ 
  7.   protected $color; 
  8.   public function __construct($color) { 
  9.     $this->color = $color; 
  10.   } 
  11.   public function beforeDraw() { 
  12.     echo "color decorator :{$this->color}\n"; 
  13.   } 
  14.   public function afterDraw() { 
  15.     echo "end color decorator\n"; 
  16.   } 
  17. } 

还有就是字体大小装饰器 Think/sizeDecorator.php,如下:

  1. <?php 
  2. /** 
  3.  * 字体大小装饰器 
  4.  */ 
  5. namespace Think; 
  6. class sizeDecorator implements decorator{ 
  7.   protected $size; 
  8.   public function __construct($size) { 
  9.     $this->size = $size; 
  10.   } 
  11.   public function beforeDraw() { 
  12.     echo "size decorator {$this->size}\n"; 
  13.   } 
  14.   public function afterDraw() { 
  15.     echo "end size decorator\n"; 
  16.   } 
  17. } 

还有被装饰者 Think/echoText.php,如下:

  1. <?php 
  2. /** 
  3.  * 被装饰者 
  4.  */ 
  5. namespace Think; 
  6. class echoText { 
  7.   protected $decorator = array(); //存放装饰器 
  8.   //装饰方法 
  9.   public function index() { 
  10.     //调用装饰器前置操作 
  11.     $this->before(); 
  12.     echo "你好,我是装饰器\n"; 
  13.     //执行装饰器后置操作 
  14.     $this->after(); 
  15.   } 
  16.   public function addDecorator(Decorator $decorator) { 
  17.     $this->decorator[] = $decorator; 
  18.   } 
  19.   //执行装饰器前置操作 先进先出 
  20.   public function before() { 
  21.     foreach ($this->decorator as $decorator){ 
  22.       $decorator->beforeDraw(); 
  23.     } 
  24.   } 
  25.   //执行装饰器后置操作 先进后出 
  26.   public function after() { 
  27.     $decorators = array_reverse($this->decorator); 
  28.     foreach ($decorators as $decorator){ 
  29.       $decorator->afterDraw(); 
  30.     } 
  31.   } 
  32. } 

再来个自动加载 Think/Loder.php,如下:

  1. <?php 
  2. namespace Think; 
  3. class Loder{ 
  4.   static function autoload($class){ 
  5.     require BASEDIR . '/' .str_replace('\\','/',$class) . '.php'; 
  6.   } 
  7. } 

最后就是入口文件index.php了,如下:

  1. <?php 
  2. define('BASEDIR',__DIR__); 
  3. include BASEDIR . '/Think/Loder.php'; 
  4. spl_autoload_register('\\Think\\Loder::autoload'); 
  5. //实例化输出类 
  6. $echo = new \Think\echoText(); 
  7. //增加装饰器 
  8. $echo->addDecorator(new \Think\colorDecorator('red')); 
  9. //增加装饰器 
  10. $echo->addDecorator(new \Think\sizeDecorator('12')); 
  11. //装饰方法 
  12. $echo->index(); 

咱最后再来一个案例啊,就是Web服务层 —— 为 REST 服务提供 JSON 和 XML 装饰器,来看代码:

RendererInterface.php

  1. <?php 
  2. namespace DesignPatterns\Structural\Decorator; 
  3. /** 
  4.  * RendererInterface接口 
  5.  */ 
  6. interface RendererInterface 
  7. { 
  8.   /** 
  9.    * render data 
  10.    * 
  11.    * @return mixed 
  12.    */ 
  13.   public function renderData(); 
  14. } 

Webservice.php

  1. <?php 
  2. namespace DesignPatterns\Structural\Decorator; 
  3. /** 
  4.  * Webservice类 
  5.  */ 
  6. class Webservice implements RendererInterface 
  7. { 
  8.   /** 
  9.    * @var mixed 
  10.    */ 
  11.   protected $data; 
  12.   /** 
  13.    * @param mixed $data 
  14.    */ 
  15.   public function __construct($data) 
  16.   { 
  17.     $this->data = $data; 
  18.   } 
  19.   /** 
  20.    * @return string 
  21.    */ 
  22.   public function renderData() 
  23.   { 
  24.     return $this->data; 
  25.   } 
  26. } 

Decorator.php

  1. <?php 
  2. namespace DesignPatterns\Structural\Decorator; 
  3. /** 
  4.  * 装饰器必须实现 RendererInterface 接口, 这是装饰器模式的主要特点, 
  5.  * 否则的话就不是装饰器而只是个包裹类 
  6.  */ 
  7. /** 
  8.  * Decorator类 
  9.  */ 
  10. abstract class Decorator implements RendererInterface 
  11. { 
  12.   /** 
  13.    * @var RendererInterface 
  14.    */ 
  15.   protected $wrapped; 
  16.   /** 
  17.    * 必须类型声明装饰组件以便在子类中可以调用renderData()方法 
  18.    * 
  19.    * @param RendererInterface $wrappable 
  20.    */ 
  21.   public function __construct(RendererInterface $wrappable) 
  22.   { 
  23.     $this->wrapped = $wrappable; 
  24.   } 
  25. } 

RenderInXml.php

  1. <?php 
  2. namespace DesignPatterns\Structural\Decorator; 
  3. /** 
  4.  * RenderInXml类 
  5.  */ 
  6. class RenderInXml extends Decorator 
  7. { 
  8.   /** 
  9.    * render data as XML 
  10.    * 
  11.    * @return mixed|string 
  12.    */ 
  13.   public function renderData() 
  14.   { 
  15.     $output = $this->wrapped->renderData(); 
  16.     // do some fancy conversion to xml from array ... 
  17.     $doc = new \DOMDocument(); 
  18.     foreach ($output as $key => $val) { 
  19.       $doc->appendChild($doc->createElement($key, $val)); 
  20.     } 
  21.     return $doc->saveXML(); 
  22.   } 
  23. } 

RenderInJson.php

  1. <?php 
  2. namespace DesignPatterns\Structural\Decorator; 
  3. /** 
  4.  * RenderInJson类 
  5.  */ 
  6. class RenderInJson extends Decorator 
  7. { 
  8.   /** 
  9.    * render data as JSON 
  10.    * 
  11.    * @return mixed|string 
  12.    */ 
  13.   public function renderData() 
  14.   { 
  15.     $output = $this->wrapped->renderData(); 
  16.     return json_encode($output); 
  17.   } 
  18. } 

Tests/DecoratorTest.php

  1. <?php 
  2. namespace DesignPatterns\Structural\Decorator\Tests; 
  3. use DesignPatterns\Structural\Decorator; 
  4. /** 
  5.  * DecoratorTest 用于测试装饰器模式 
  6.  */ 
  7. class DecoratorTest extends \PHPUnit_Framework_TestCase 
  8. { 
  9.   protected $service; 
  10.   protected function setUp() 
  11.   { 
  12.     $this->service = new Decorator\Webservice(array('foo' => 'bar')); 
  13.   } 
  14.   public function testJsonDecorator() 
  15.   { 
  16.     // Wrap service with a JSON decorator for renderers 
  17.     $service = new Decorator\RenderInJson($this->service); 
  18.     // Our Renderer will now output JSON instead of an array 
  19.     $this->assertEquals('{"foo":"bar"}', $service->renderData()); 
  20.   } 
  21.   public function testXmlDecorator() 
  22.   { 
  23.     // Wrap service with a XML decorator for renderers 
  24.     $service = new Decorator\RenderInXml($this->service); 
  25.     // Our Renderer will now output XML instead of an array 
  26.     $xml = '<?xml version="1.0"?><foo>bar</foo>'; 
  27.     $this->assertXmlStringEqualsXmlString($xml, $service->renderData()); 
  28.   } 
  29.   /** 
  30.    * The first key-point of this pattern : 
  31.    */ 
  32.   public function testDecoratorMustImplementsRenderer() 
  33.   { 
  34.     $className = 'DesignPatterns\Structural\Decorator\Decorator'; 
  35.     $interfaceName = 'DesignPatterns\Structural\Decorator\RendererInterface'; 
  36.     $this->assertTrue(is_subclass_of($className, $interfaceName)); 
  37.   } 
  38.   /** 
  39.    * Second key-point of this pattern : the decorator is type-hinted 
  40.    * 
  41.    * @expectedException \PHPUnit_Framework_Error 
  42.    */ 
  43.   public function testDecoratorTypeHinted() 
  44.   { 
  45.     if (version_compare(PHP_VERSION, '7', '>=')) { 
  46.       throw new \PHPUnit_Framework_Error('Skip test for PHP 7', 0, __FILE__, __LINE__); 
  47.     } 
  48.     $this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array(new \stdClass())); 
  49.   } 
  50.   /** 
  51.    * Second key-point of this pattern : the decorator is type-hinted 
  52.    * 
  53.    * @requires PHP 7 
  54.    * @expectedException TypeError 
  55.    */ 
  56.   public function testDecoratorTypeHintedForPhp7() 
  57.   { 
  58.     $this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array(new \stdClass())); 
  59.   } 
  60.   /** 
  61.    * The decorator implements and wraps the same interface 
  62.    */ 
  63.   public function testDecoratorOnlyAcceptRenderer() 
  64.   { 
  65.     $mock = $this->getMock('DesignPatterns\Structural\Decorator\RendererInterface'); 
  66.     $dec = $this->getMockForAbstractClass('DesignPatterns\Structural\Decorator\Decorator', array($mock)); 
  67.     $this->assertNotNull($dec); 
  68.   } 
  69. } 

好啦,本次记录就到这里了。

Tags: PHP装饰器 Decorator

分享到: