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

吾爱主题 阅读:152 2021-09-23 16:09:00 评论:0

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

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

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

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

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

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

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

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 /** * 被修饰类 现在的需求: 要求能够动态为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

?
1 2 3 4 5 6 7 8 9 10 11 12 //装饰器模式,对已有对象的部分内容或者功能进行调整,但是不需要修改原始对象结构,可以使用装饰器设计模式 class UserInfo {   public $userInfo = array ();     public function addUser( $userInfo ) {   $this ->userInfo[] = $userInfo ;   }     public function getUserList() {   print_r( $this ->userInfo);   } }
?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 //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,如下:

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

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

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?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,如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?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,如下:

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?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,如下:

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

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

?
1 2 3 4 5 6 7 8 9 10 11 12 <?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

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

Webservice.php

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <?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

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <?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

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?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

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?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

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 <?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 );    } }

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

希望本文所述对大家PHP程序设计有所帮助。

原文链接:https://blog.csdn.net/luyaran/article/details/82883411

可以去百度分享获取分享代码输入这里。
声明

1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。

【腾讯云】云服务器产品特惠热卖中
搜索
标签列表
    关注我们

    了解等多精彩内容