Декоратор (англ. Decorator) относиться к классу структурных шаблонов. Он используется для динамического расширения
функциональности объекта. Является гибкой альтернативой наследованию.
Смысл заключается в том, чтобы можно было безболезненно комбинировать различные декораторы в произвольном порядке, навешивая их на различные объекты. В некотором роде, это похоже на технологию traits, за исключением того, что декораторы динамически навешиваются на объект, а traits статически на класс.
На примере мы имеем класс Cheeseburger
, который реализует интерфейс BurgerInterface
.
Тут мы замечаем, что в классе Cheeseburger
не хватает сыра. Это основной ингредиент чизбургера и хорошо бы нам дописать
его в класс. Но давайте представим, что переписывать класс мы не можем. “Давайте наследуем Cheeseburger
и расширим его”, скажете вы.
А если нам нужно ещё что-то добавить к Cheeseburger
? Опять наследование.
А если нужно это всё объединить и ещё что-то добавить? Опять наследование. Потом опять и опять…
Декоратор помогает нам сделать это всё более гибко.
Так как наш абстрактный декоратор AbstractDecorator
реализует интерфейс BurgerInterface
,
мы можем расширять его на уровне абстракции.
Добавим сыр средствами CheeseburgerCheeseDecorator
.
Наш декоратор (наследник AbstractDecorator
), в конструктор ждёт объект типа BurgerInterface
.
Как упоминалось выше, AbstractDecorator
реализует интерфейс BurgerInterface
. Но метод BurgerInterface->makeBurger()
он не реализует, а помечает как абстрактный. Это значит что наследники должны реализовать его.
Вот в нём мы будем декорировать наш чизбургер.
А теперь давайте поменяем соус в чизбургере.
Создаем новый декоратор CheeseburgerSauceDecorator
(наследник AbstractDecorator
).
Теперь мы можем расширять любой объект типа BurgerInterface
.
Это может быть как исходный чизбургер без сыра(объект Cheeseburger->makeBurger()
), так и уже декорированный с сыром (CheeseburgerCheeseDecorator->makeBurger()
).
-
Burgers
-
BurgerInterface.php
<?php namespace DesignPatterns\Structural\Decorator\Burgers; /** * Interface BurgerInterface * @package DesignPatterns\Structural\Decorator\Burgers */ interface BurgerInterface { /** * @return array */ public function makeBurger(): array; }
-
Cheeseburger.php
<?php namespace DesignPatterns\Structural\Decorator\Burgers; /** * Class Cheeseburger * @package DesignPatterns\Structural\Decorator\Burgers */ class Cheeseburger implements BurgerInterface { /** * @var string */ protected $meat = 'chicken'; /** * @var string */ protected $sauce = 'mayonnaise'; /** * @var string */ protected $muffin = 'muffin'; /** * @return array */ public function makeBurger(): array { return [ 'muffin' => $this->muffin, 'sauce' => $this->sauce, 'meat' => $this->meat, ]; } }
-
-
AbstractDecorator.php
<?php namespace DesignPatterns\Structural\Decorator; use DesignPatterns\Structural\Decorator\Burgers\BurgerInterface; /** * Class AbstractDecorator * @package DesignPatterns\Structural\Decorator */ abstract class AbstractDecorator implements BurgerInterface { /** * @var BurgerInterface */ protected $burger; /** * AbstractDecorator constructor. * * @param BurgerInterface $burger */ public function __construct(BurgerInterface $burger) { $this->burger = $burger; } /** * @return array */ abstract public function makeBurger(): array; }
-
CheeseburgerCheeseDecorator.php
<?php namespace DesignPatterns\Structural\Decorator; /** * Class CheeseburgerCheeseDecorator * @package DesignPatterns\Structural\Decorator */ class CheeseburgerCheeseDecorator extends AbstractDecorator { const BEST_CHEESE_FOR_BURGER = 'Cheddar'; /** * @return array */ public function makeBurger(): array { $burger = $this->burger->makeBurger(); return array_replace( $burger, [ 'cheese' => self::BEST_CHEESE_FOR_BURGER, ] ); } }
-
CheeseburgerSauceDecorator.php
<?php namespace DesignPatterns\Structural\Decorator; /** * Class CheeseburgerSauceDecorator * @package DesignPatterns\Structural\Decorator */ class CheeseburgerSauceDecorator extends AbstractDecorator { const NEW_SAUCE = 'Ukrainian sauce'; /** * @return array */ public function makeBurger(): array { $burger = $this->burger->makeBurger(); return array_replace( $burger, [ 'sauce' => self::NEW_SAUCE, ] ); } }
Test case:
DecoratorTest.php
<?php
namespace DesignPatterns\Tests\Structural\Decorator;
use DesignPatterns\Structural\Decorator\Burgers\Cheeseburger;
use DesignPatterns\Structural\Decorator\CheeseburgerCheeseDecorator;
use DesignPatterns\Structural\Decorator\CheeseburgerSauceDecorator;
class DecoratorTest extends \PHPUnit_Framework_TestCase
{
public function testAddCheeseDecorator()
{
$cheeseburger = new Cheeseburger();
$cheeseDecorator = new CheeseburgerCheeseDecorator($cheeseburger);
$this->assertArrayNotHasKey('cheese', $cheeseburger->makeBurger());
$this->assertArrayHasKey('cheese', $cheeseDecorator->makeBurger());
}
public function testAddSauceDecorator()
{
$cheeseburger = new Cheeseburger();
$sauceDecorator = new CheeseburgerSauceDecorator($cheeseburger);
$cheeseburger = $cheeseburger->makeBurger();
$decorateCheeseburger = $sauceDecorator->makeBurger();
$this->assertArrayHasKey('sauce', $cheeseburger);
$this->assertArrayHasKey('sauce', $decorateCheeseburger);
$this->assertEquals('mayonnaise', $cheeseburger['sauce']);
$this->assertNotEquals('mayonnaise', $decorateCheeseburger['sauce']);
$this->assertEquals($sauceDecorator::NEW_SAUCE, $decorateCheeseburger['sauce']);
}
public function testAddSauceAndCheeseDecorator()
{
$cheeseburger = new Cheeseburger();
$cheeseDecorator = new CheeseburgerCheeseDecorator($cheeseburger);
$sauceDecorator = new CheeseburgerSauceDecorator($cheeseDecorator);
$decorateCheeseburger = $sauceDecorator->makeBurger();
$this->assertArrayHasKey('cheese', $decorateCheeseburger);
$this->assertArrayHasKey('cheese', $decorateCheeseburger);
$this->assertEquals($sauceDecorator::NEW_SAUCE, $decorateCheeseburger['sauce']);
}
}