19
2014PHP паттерн - Декоратор
Довольно много программистов читали или по крайней мере слышали о книге банды четырех паттерны проектирования. Зачем эти паттерны вообще нужны, спросите вы ? Отвечу - они не вносят дополнительный функционал в язык программирования, однако они вносят ясность в вопросы, повседневно, решаемые программистами.
В этой статье, я хочу познакомить вас с интересным и полезным паттерном - Декоратор. Я предполагаю, вы могли встречаться с ним изучая код проектов с исходным кодом или просто услышав от коллег-программистов. Давайте "декорировать" вместе!
Что же такое Декоратор ?
Декоратор - это паттерн, или шаблон (кому как удобнее), позволяющий изменить функционал класса, не изменяя структуру. Благодаря таким возможностям ООП, как наследование, интерфейсы и абстрактный класс мы можем пользоваться данным паттерном.
Ну что ж, начнем. Предположим, что у нас есть класс File, который реализует интерфейс FileInterface и мы хотим получать ссылку на файл или путь к нему "легким движением руки". Ниже я представил коды класса File и интерфейса FileInterface.
interface FileInterface { /** * Get Path * * @return string */ public function getPath(); } class File implements FileInterface { /** @var string */ protected $file; /** @var string */ protected $url; /** * Construct */ public function __construct($file, $url) { $this->file = $file; $this->url = $url; } /** * Get path */ public function getPath() { return $this->file; } /** * Get URL */ public function getUrl() { return $this->url; } }
Класс File, реализующий интерфейс FileInterface содержит два свойства - file и url, и методы работы с ними. Таким образом, мы можем воспользоваться методом getPath, чтобы получить путь к файлу, или getUrl, для получения ссылки на файл.
Данная реализация имеет право на существование, однако если потребуется работать не с ссылкой или путем, а через SSH ? Придется дописывать класс, что в свою очередь может вызвать неработоспособность уже написанных и отлаженных частей.
Именно для того чтобы этого избежать и используется паттерн декоратор.
Приступим к реализации данного паттерна. Вначале, создадим общий абстрактный класс, от которого и будем расширять частные класс.
class AbstractFileDecorator { /** @var File */ protected $file; /** * Construct */ public function __construct(File $file) { $this->file = $file->file; } /** * Get path */ public function getPath() { return $this->file->getPath(); } }
Теперь, когда у нас есть абстрактный класс, мы можем расширить его, дополнив нужными нам действиям.
class FileSystemDecorator extends AbstractFileDecorator implements FileInterface { /** * Get Path * * @return string */ public function getPath() { return $this->file->getPath(); } }
FileSystemDecorator реализует базовый функционал оригинального класса.
class UrlFileDecorator extends AbstractFileDecorator implements FileInterface { /** * Get Path * * @return string */ public function getPath() { return 'http://example.com'.$this->url; } }
Когда, мы запрашиваем файл, UrlFileDecorator будет возвращать url, а не путь к файлу.
class SecureUrlFileDecorator extends AbstractFileDecorator implements FileInterface { /** * Get Path * * @return string */ public function getPath() { return 'https://example.com'.$this->url; } }
Для SecureUrlFileDecorator используется https протокол.
class SshFileDecorator extends AbstractFileDecorator implements FileInterface { /** * Get Path * * @return string */ public function getPath() { return 'user@example.com:'.$this->file; } }
И наконец, для SshFileDecorator мы можем определить SSH информацию. Очевидно, что вы бы могли расширить базовый функционал данных классов и без труда бы разобрались что происходит в коде.
Использование паттерна
Мы рассмотрели большую часть процесса создания классов, теперь приходит время, чтобы начать им пользоваться. Для начала создаем файл.
$file = new File('/var/www/my-file.jpg', '/images/my-file.jpg');
А теперь рассмотрим примеры использования на примерах:
// Получить файл на сервере $server = new FileSystemDecorator($file); $server->getPath(); // /var/www/my-file.jpg // Получить ссылку $browser = new UrlFileDecorator($file); $browser->getPath(); // http://example.com/images/my-file.jpg // То же через HTTPS $https = new SecureUrlFileDecorator($file); $https->getPath(); // https://example.com/images/my-file.jpg // То же через SSH $ssh = new SshFileDecorator($file); $ssh->getPath(); // 'user@example.com:'/var/www/my-file.jpg
Расширяем декоратор
Теперь, когда у нас уже есть базовые классы, мы можем дополнить его, ведь это не все что можно сделать. Например мы можем дополнить наш декоратор классом - CacheDecorator, который бы мог возвращать файл из кеша, CDN и других источников, а не через файловую систему, или например LoggerDecorator, который мог бы записывать действия производимые с файлом.
Помните, что когда вы создаете новый декоратор, вы просто должны расширить объект, не изменяя при этом уже существующие классы.
Когда следует использовать декоратор ?
Надеюсь мой пример достаточно хорош, чтобы показать каким может быть декоратор и как его просто и удобно использовать. Хочу сказать, что данный паттерн хорошо подходит для изменения класса во время выполнения. Так, например, в приведенном выше примере, можно изменить выходные данные класса в зависимости от среды, которой используется данный класс.
В заключении хочу сказать, что вместо декоратором мы могли бы воспользоваться маленькими классами, цель существования которых - одно единственное действие. Однако поддержка проекта с такими "крошками" может вызвать довольно большие сложности, особенно при изменении или удалении действий.
Автор: Philip Brown Оригинал: http://culttt.com/2014/04/23/decorator-pattern/ Перевод: Андрей Николаев
Меня зовут Андрей Николаев, я разработчик-фрилансер с более чем четырехлетним опытом работы в сфере веб-технологий. Последнее время занимаюсь разработкой и поддержкой lowload и midload проектов, разработкой веб-сайтов на платформе 1С-Битрикс, Yii, Laravel. Предоставляю услуги как разработчика сайта, так и консультанта, а при необходимости и менеджера проекта со стороны заказчика.
Обо мне