当前位置:首页 > 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.   } 

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

  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. $myCD=new CD(); 
  14. $trackList=array(  "what It Means",  "brr",  "goodBye" ); 
  15. foreach ($trackList as $key => $value) { 
  16.   # code... 
  17.   $myCD->addTrack($value); 
  18. $myCDCaps=new CDTrackListDecoratorCaps($myCD); 
  19. $myCDCaps->makeCaps(); 
  20. 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.  } 

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. $UserInfo = new UserInfo; 
  12. $UserInfo->addUser('zhu'); 
  13. $UserInfo->addUser('initphp'); 
  14. $UserInfoDecorate = new UserInfoDecorate; 
  15. $UserInfoDecorate->makeCaps($UserInfo); 
  16. $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(); 

再来就是颜色装饰器 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.   } 

还有就是字体大小装饰器 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.   } 

还有被装饰者 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.   } 

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

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

最后就是入口文件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.    * render data 
  9.    * 
  10.    * @return mixed 
  11.    */ 
  12.   public function renderData(); 

Webservice.php

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

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.    * @var RendererInterface 
  13.    */ 
  14.   protected $wrapped
  15.   /** 
  16.    * 必须类型声明装饰组件以便在子类中可以调用renderData()方法 
  17.    * 
  18.    * @param RendererInterface $wrappable 
  19.    */ 
  20.   public function __construct(RendererInterface $wrappable
  21.   { 
  22.     $this->wrapped = $wrappable
  23.   } 

RenderInXml.php

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

RenderInJson.php

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

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

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

Tags: PHP装饰器 Decorator

分享到: