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

Jul

19

2014

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

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

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

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

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

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

Обо мне