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

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

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

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

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

  • 1、我们可以保证类的层次不会因过多而发生混乱。
  • 2、当我们需求的修改很小时,不用改变原有的数据结构。

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

/** * 被修饰类 现在的需求: 要求能够动态为cd添加音轨、能显示cd音轨列表。 显示时应采用单行并且为每个音轨都以音轨好为前缀。 */
class cd {
  public $tracklist;
  function __construct()  {
    # code...
    $this->tracklist=array();
  }
  public function addtrack($track){
    $this->tracklist[]=$track;
  }
  public function gettracklist(){
    $output=" ";
    foreach ($this->tracklist as $key => $value) {
      # code...
      $output.=($key+1).") {$value}. ";
    }
    return $output;
  }
}
/* 现在需求发生变化: 要求将当前实例输出的音轨都采用大写形式。 这个需求并不是一个变化特别大的需求,不需要修改基类或创建一个父子关系的子类,此时创建一个基于装饰器模式的装饰器类。 */
class cdtracklistdecoratorcaps{
  private $_cd;
  public function __construct(cd $cd){
    $this->_cd=$cd;
  }
  public function makecaps(){
    foreach ($this->_cd->tracklist as $key => $value) {
      # code...
      $this->_cd->tracklist[$key]=strtoupper($value); //转换成大写
    }
  }
}
//客户端测试
$mycd=new cd();
$tracklist=array(  "what it means",  "brr",  "goodbye" );
foreach ($tracklist as $key => $value) {
  # code...
  $mycd->addtrack($value);
}
$mycdcaps=new cdtracklistdecoratorcaps($mycd);
$mycdcaps->makecaps();
print "the cd contains the following tracks:".$mycd->gettracklist();

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

  • 设计一个userinfo类,里面有userinfo数组,用于存储用户名信息
  • 通过adduser来添加用户名
  • getuserlist方法将打印出用户名信息
  • 现在需要将添加的用户信息变成大写的,我们需要不改变原先的类,并且不改变原先的数据结构
  • 我们设计了一个userinfodecorate类来完成这个需求的操作,就像装饰一样,给原先的数据进行了装修
  • 装饰器模式有些像适配器模式,但是一定要注意,装饰器主要是不改变现有对象数据结构的前提

代码如下:

userinfo.php

//装饰器模式,对已有对象的部分内容或者功能进行调整,但是不需要修改原始对象结构,可以使用装饰器设计模式
class userinfo {
 public $userinfo = array(); 
 
 public function adduser($userinfo) {
 $this->userinfo[] = $userinfo;
 }
 
 public function getuserlist() {
 print_r($this->userinfo);
 }
}
//userinfodecorate 装饰一样,改变用户信息输出为大写格式,不改变原先userinfo类
<?php
include("userinfo.php");
class userinfodecorate {
 
 public function makecaps($userinfo) {
 foreach ($userinfo->userinfo as &$val) {
  $val = strtoupper($val);
 }
 }
 
}
$userinfo = new userinfo;
$userinfo->adduser('zhu');
$userinfo->adduser('initphp');
$userinfodecorate = new userinfodecorate;
$userinfodecorate->makecaps($userinfo);
$userinfo->getuserlist();

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

|decorator  #项目根目录
|–think  #核心类库
|—-loder.php  #自动加载类
|—-decorator.php  #装饰器接口
|—-colordecorator.php  #颜色装饰器
|—-sizedecorator.php  #字体大小装饰器
|—-echotext.php  #被装饰者
|–index.php #单一的入口文件

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

<?php
/**
 * 装饰器接口
 * interface decorator
 * @package think
 */
namespace think;
interface decorator{
  public function beforedraw();
  public function afterdraw();
}

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

<?php
/**
 * 颜色装饰器
 */
namespace think;
class colordecorator implements decorator{
  protected $color;
  public function __construct($color) {
    $this->color = $color;
  }
  public function beforedraw() {
    echo "color decorator :{$this->color}\n";
  }
  public function afterdraw() {
    echo "end color decorator\n";
  }
}

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

<?php
/**
 * 字体大小装饰器
 */
namespace think;
class sizedecorator implements decorator{
  protected $size;
  public function __construct($size) {
    $this->size = $size;
  }
  public function beforedraw() {
    echo "size decorator {$this->size}\n";
  }
  public function afterdraw() {
    echo "end size decorator\n";
  }
}

还有被装饰者 think/echotext.php,如下:

<?php
/**
 * 被装饰者
 */
namespace think;
class echotext {
  protected $decorator = array(); //存放装饰器
  //装饰方法
  public function index() {
    //调用装饰器前置操作
    $this->before();
    echo "你好,我是装饰器\n";
    //执行装饰器后置操作
    $this->after();
  }
  public function adddecorator(decorator $decorator) {
    $this->decorator[] = $decorator;
  }
  //执行装饰器前置操作 先进先出
  public function before() {
    foreach ($this->decorator as $decorator){
      $decorator->beforedraw();
    }
  }
  //执行装饰器后置操作 先进后出
  public function after() {
    $decorators = array_reverse($this->decorator);
    foreach ($decorators as $decorator){
      $decorator->afterdraw();
    }
  }
}

再来个自动加载 think/loder.php,如下:

<?php
namespace think;
class loder{
  static function autoload($class){
    require basedir . '/' .str_replace('\\','/',$class) . '.php';
  }
}

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

<?php
define('basedir',__dir__);
include basedir . '/think/loder.php';
spl_autoload_register('\\think\\loder::autoload');
//实例化输出类
$echo = new \think\echotext();
//增加装饰器
$echo->adddecorator(new \think\colordecorator('red'));
//增加装饰器
$echo->adddecorator(new \think\sizedecorator('12'));
//装饰方法
$echo->index();

咱最后再来一个案例啊,就是web服务层 —— 为 rest 服务提供 json 和 xml 装饰器,来看代码:

rendererinterface.php

<?php
namespace designpatterns\structural\decorator;
/**
 * rendererinterface接口
 */
interface rendererinterface
{
  /**
   * render data
   *
   * @return mixed
   */
  public function renderdata();
}

webservice.php

<?php
namespace designpatterns\structural\decorator;
/**
 * webservice类
 */
class webservice implements rendererinterface
{
  /**
   * @var mixed
   */
  protected $data;
  /**
   * @param mixed $data
   */
  public function __construct($data)
  {
    $this->data = $data;
  }
  /**
   * @return string
   */
  public function renderdata()
  {
    return $this->data;
  }
}

decorator.php

<?php
namespace designpatterns\structural\decorator;
/**
 * 装饰器必须实现 rendererinterface 接口, 这是装饰器模式的主要特点,
 * 否则的话就不是装饰器而只是个包裹类
 */
/**
 * decorator类
 */
abstract class decorator implements rendererinterface
{
  /**
   * @var rendererinterface
   */
  protected $wrapped;
  /**
   * 必须类型声明装饰组件以便在子类中可以调用renderdata()方法
   *
   * @param rendererinterface $wrappable
   */
  public function __construct(rendererinterface $wrappable)
  {
    $this->wrapped = $wrappable;
  }
}

renderinxml.php

<?php
namespace designpatterns\structural\decorator;
/**
 * renderinxml类
 */
class renderinxml extends decorator
{
  /**
   * render data as xml
   *
   * @return mixed|string
   */
  public function renderdata()
  {
    $output = $this->wrapped->renderdata();
    // do some fancy conversion to xml from array ...
    $doc = new \domdocument();
    foreach ($output as $key => $val) {
      $doc->appendchild($doc->createelement($key, $val));
    }
    return $doc->savexml();
  }
}

renderinjson.php

<?php
namespace designpatterns\structural\decorator;
/**
 * renderinjson类
 */
class renderinjson extends decorator
{
  /**
   * render data as json
   *
   * @return mixed|string
   */
  public function renderdata()
  {
    $output = $this->wrapped->renderdata();
    return json_encode($output);
  }
}

tests/decoratortest.php

<?php
namespace designpatterns\structural\decorator\tests;
use designpatterns\structural\decorator;
/**
 * decoratortest 用于测试装饰器模式
 */
class decoratortest extends \phpunit_framework_testcase
{
  protected $service;
  protected function setup()
  {
    $this->service = new decorator\webservice(array('foo' => 'bar'));
  }
  public function testjsondecorator()
  {
    // wrap service with a json decorator for renderers
    $service = new decorator\renderinjson($this->service);
    // our renderer will now output json instead of an array
    $this->assertequals('{"foo":"bar"}', $service->renderdata());
  }
  public function testxmldecorator()
  {
    // wrap service with a xml decorator for renderers
    $service = new decorator\renderinxml($this->service);
    // our renderer will now output xml instead of an array
    $xml = '<?xml version="1.0"?><foo>bar</foo>';
    $this->assertxmlstringequalsxmlstring($xml, $service->renderdata());
  }
  /**
   * the first key-point of this pattern :
   */
  public function testdecoratormustimplementsrenderer()
  {
    $classname = 'designpatterns\structural\decorator\decorator';
    $interfacename = 'designpatterns\structural\decorator\rendererinterface';
    $this->asserttrue(is_subclass_of($classname, $interfacename));
  }
  /**
   * second key-point of this pattern : the decorator is type-hinted
   *
   * @expectedexception \phpunit_framework_error
   */
  public function testdecoratortypehinted()
  {
    if (version_compare(php_version, '7', '>=')) {
      throw new \phpunit_framework_error('skip test for php 7', 0, __file__, __line__);
    }
    $this->getmockforabstractclass('designpatterns\structural\decorator\decorator', array(new \stdclass()));
  }
  /**
   * second key-point of this pattern : the decorator is type-hinted
   *
   * @requires php 7
   * @expectedexception typeerror
   */
  public function testdecoratortypehintedforphp7()
  {
    $this->getmockforabstractclass('designpatterns\structural\decorator\decorator', array(new \stdclass()));
  }
  /**
   * the decorator implements and wraps the same interface
   */
  public function testdecoratoronlyacceptrenderer()
  {
    $mock = $this->getmock('designpatterns\structural\decorator\rendererinterface');
    $dec = $this->getmockforabstractclass('designpatterns\structural\decorator\decorator', array($mock));
    $this->assertnotnull($dec);
  }
}

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