欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2023-11-30 10:07:58
本文实例讲述了php设计模式之装饰器(装饰者)模式(decorator)入门与应用。分享给大家供大家参考,具体如下: 通常情况下,我们如果要给对象添加功能,要么直接修改对象添加相应的...

本文实例讲述了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);
  }
}

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