PHP设计模式(七)-装饰器模式


layout: post
title: "PHP设计模式(七)-装饰器模式"
date: 2016-06-07 10:53:23 +0800
comments: true
categories: [php]


1、模式定义

装饰器模式能够从一个对象的外部动态地给对象添加功能。

通常给对象添加功能,要么直接修改对象添加相应的功能,要么派生对应的子类来扩展,抑或是使用对象组合的方式。显然,直接修改对应的类这种方式并不可取。在面向对象的设计中,我们也应该尽量使用对象组合,而不是对象继承来扩展和复用功能。装饰器模式就是基于对象组合的方式,可以很灵活的给对象添加所需要的功能。装饰器模式的本质就是动态组合。动态是手段,组合才是目的。

常见的使用示例:Web服务层 —— 为 REST 服务提供 JSON 和 XML 装饰器。

2、UML类图

1107940-1c8db13642084204.png

image

3、实例代码

定义接口:

    <?php
    
    namespace DesignPatterns\Structural\Decorator;
    
    /**
     * RendererInterface接口
     */
    interface RendererInterface
    {
        /**
         * render data
         *
         * @return mixed
         */
        public function renderData();
    }
    <?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;
        }
    }

个人感觉在这里没有必要实现RendererInterface接口,
实现这个接口过后就必须实现renderData方法,而这个方法仅仅是用来返回数据。
但是这个名字和后面装饰器的实现方法重名,很容易混乱。
这里如果不实现RendererInterface接口,将renderData()改为getData()应该会好一些,至少程序没那么容易搞混。

装饰器抽象类:

    <?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;
        }
    }
    <?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();
        }
    }
    <?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);
        }
    }

4、测试代码

    <?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'));//实例化 Webservice 类
        }
    
        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);
        }
    } 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值