Декоратор Шаблон проектирования

Jul

19

2014

PHP паттерн - Декоратор

Довольно много программистов читали или по крайней мере слышали о книге банды четырех паттерны проектирования. Зачем эти паттерны вообще нужны, спросите вы ? Отвечу - они не вносят дополнительный функционал в язык программирования, однако они вносят ясность в вопросы, повседневно, решаемые программистами.

В этой статье, я хочу познакомить вас с интересным и полезным паттерном - Декоратор. Я предполагаю, вы могли встречаться с ним изучая код проектов с исходным кодом или просто услышав от коллег-программистов. Давайте "декорировать" вместе!

Что же такое Декоратор ?

Декоратор - это паттерн, или шаблон (кому как удобнее), позволяющий изменить функционал класса, не изменяя структуру. Благодаря таким возможностям ООП, как наследование, интерфейсы и абстрактный класс мы можем пользоваться данным паттерном.

Ну что ж, начнем. Предположим, что у нас есть класс 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() {
        return 'http://example.com'.$this->url;
    }
}

Когда, мы запрашиваем файл, 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() {
        return 'https://example.com'.$this->url;
    }
}

Для 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. Предоставляю услуги как разработчика сайта, так и консультанта, а при необходимости и менеджера проекта со стороны заказчика.

Обо мне