Декоратор (англ. 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']);
}
}