19
2014PHP паттерн - Декоратор
Довольно много программистов читали или по крайней мере слышали о книге банды четырех паттерны проектирования. Зачем эти паттерны вообще нужны, спросите вы ? Отвечу - они не вносят дополнительный функционал в язык программирования, однако они вносят ясность в вопросы, повседневно, решаемые программистами.
В этой статье, я хочу познакомить вас с интересным и полезным паттерном - Декоратор. Я предполагаю, вы могли встречаться с ним изучая код проектов с исходным кодом или просто услышав от коллег-программистов. Давайте "декорировать" вместе!
Что же такое Декоратор ?
Декоратор - это паттерн, или шаблон (кому как удобнее), позволяющий изменить функционал класса, не изменяя структуру. Благодаря таким возможностям ООП, как наследование, интерфейсы и абстрактный класс мы можем пользоваться данным паттерном.
Ну что ж, начнем. Предположим, что у нас есть класс File, который реализует интерфейс FileInterface и мы хотим получать ссылку на файл или путь к нему "легким движением руки". Ниже я представил коды класса File и интерфейса FileInterface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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 ? Придется дописывать класс, что в свою очередь может вызвать неработоспособность уже написанных и отлаженных частей.
Именно для того чтобы этого избежать и используется паттерн декоратор.
Приступим к реализации данного паттерна. Вначале, создадим общий абстрактный класс, от которого и будем расширять частные класс.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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(); } } |
Теперь, когда у нас есть абстрактный класс, мы можем расширить его, дополнив нужными нам действиям.
1 2 3 4 5 6 7 8 9 10 | class FileSystemDecorator extends AbstractFileDecorator implements FileInterface { /** * Get Path * * @return string */ public function getPath() { return $this ->file->getPath(); } } |
FileSystemDecorator реализует базовый функционал оригинального класса.
1 2 3 4 5 6 7 8 9 10 | class UrlFileDecorator extends AbstractFileDecorator implements FileInterface { /** * Get Path * * @return string */ public function getPath() { } } |
Когда, мы запрашиваем файл, UrlFileDecorator будет возвращать url, а не путь к файлу.
1 2 3 4 5 6 7 8 9 10 | class SecureUrlFileDecorator extends AbstractFileDecorator implements FileInterface { /** * Get Path * * @return string */ public function getPath() { } } |
Для SecureUrlFileDecorator используется https протокол.
1 2 3 4 5 6 7 8 9 10 | class SshFileDecorator extends AbstractFileDecorator implements FileInterface { /** * Get Path * * @return string */ public function getPath() { return 'user@example.com:' . $this ->file; } } |
И наконец, для SshFileDecorator мы можем определить SSH информацию. Очевидно, что вы бы могли расширить базовый функционал данных классов и без труда бы разобрались что происходит в коде.
Использование паттерна
Мы рассмотрели большую часть процесса создания классов, теперь приходит время, чтобы начать им пользоваться. Для начала создаем файл.
1 | $file = new File( '/var/www/my-file.jpg' , '/images/my-file.jpg' ); |
А теперь рассмотрим примеры использования на примерах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | // Получить файл на сервере $server = new FileSystemDecorator( $file ); $server ->getPath(); // /var/www/my-file.jpg // Получить ссылку $browser = new UrlFileDecorator( $file ); // То же через HTTPS $https = new SecureUrlFileDecorator( $file ); // То же через 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. Предоставляю услуги как разработчика сайта, так и консультанта, а при необходимости и менеджера проекта со стороны заказчика.