1. 模式定义
装饰器模式能够从一个对象的外部动态地给对象添加功能。
一般给一个对象加功能有如下三种方式:
- 直接修改这个对象 => 这是不可取的,会影响其他调用这个对象的类或对象
- 派生对于的子类来拓展 => 随着扩展越多,子类会越膨胀
- 对象组合 √
装饰器模式就是采用动态组合的方式,实现给一个对象加功能。
最常见的示例是 Web服务层——为REST 服务提供json 和xml装饰器
2. UML类图
3. 示例代码
RenderInterface.php
<?php
namespace DesignPattern\Structural\Decorator;
/**
* 渲染器接口
* Class RendererInterface
* @package DesignPattern\Structural\Decorator
*/
interface RendererInterface
{
//渲染数据
public function renderData();
}
WebService.php
<?php
namespace DesignPattern\Structural\Decorator;
class WebService implements RendererInterface
{
protected $data;
public function __construct($data)
{
$this->data = $data;
}
public function renderData()
{
return $this->data;
}
}
Decorator.php
<?php
namespace DesignPattern\Structural\Decorator;
/**
* 装饰器: 必须实现 RendererInterface 接口, 这是装饰器模式的主要特点,
* 否则的话就不是装饰器而只是个包裹类
* Class Decorator
* @package DesignPattern\Structural\Decorator
*/
abstract class Decorator implements RendererInterface
{
/** @var $renderer RendererInterface */
protected $renderer;
public function __construct(RendererInterface $renderer)
{
$this->renderer = $renderer;
}
}
RenderXml .php
<?php
namespace DesignPattern\Structural\Decorator;
/**
* 渲染成XML
* Class RenderXml
* @package DesignPattern\Structural\Decorator
*/
class RenderXml extends Decorator
{
public function renderData()
{
$data = $this->renderer->renderData();
$doc = new \DOMDocument();
foreach ($data as $key => $val) {
$doc->appendChild($doc->createElement($key, $val));
}
return $doc->saveXML();
}
}
RenderJson.php
<?php
namespace DesignPattern\Structural\Decorator;
/**
* 渲染成 Json
* Class RenderXml
* @package DesignPattern\Structural\Decorator
*/
class RenderJson extends Decorator
{
public function renderData()
{
$output = $this->renderer->renderData();
return json_encode($output);
}
}
DecoratorTest
<?php
namespace DesignPattern\Tests;
use DesignPattern\Structural\Decorator\RenderJson;
use DesignPattern\Structural\Decorator\RenderXml;
use DesignPattern\Structural\Decorator\WebService;
use PHPUnit\Framework\TestCase;
class DecoratorTest extends TestCase
{
protected $service;
protected function setUp(): void
{
$this->service = new WebService(array('foo' => 'bar'));
}
public function testJsonDecorator()
{
// Wrap service with a JSON decorator for renderers
$service = new RenderJson($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 RenderXml($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 = 'DesignPattern\Structural\Decorator\Decorator';
$interfaceName = 'DesignPattern\Structural\Decorator\RendererInterface';
$this->assertTrue(is_subclass_of($className, $interfaceName));
}
}
注:参考文档:https://laravelacademy.org/post/2760
教程源码:https://github.com/SylviaYuan1995/DesignPatternDemo