Декоратор — это специалист по художественному оформлению интерьера и экстерьера. Он делает все красивым: квартиру, торговый центр, личный дворик, большие ландшафты и др. При чем здесь Декоратор и программирование? — спросите вы.
В программировании также есть собственный «специалист по декору» — это паттерн «Декоратор». Паттерн «Декоратор» — это структурный шаблон в программировании, который открывает возможность подключать дополнительную функциональность к объектам в динамическом режиме. Паттерн «Декоратор» свойственен многим языкам программирования. По своей концепции он чем-то напоминает наследование свойств или создание подклассов, однако предлагает более гибкие возможности определения дополнительных характеристик объектам.
Паттерн «Декоратор»
Паттерн «Декоратор» применяется в том случае, когда объекту необходимо придать какие-то дополнительные свойства в динамическом режиме. Но когда потребность в этих свойствах исчезнет, они должны быть сняты с объекта. Также он применяется в том случае, когда невозможно применить наследование. Например:
-
когда объекту нужно придать множество разных свойств, а для каждого свойства нужно определить собственный класс, таким образом, количество классов очень сильно «раздуется»;
-
когда с объекта нужно «снять» весь основной функционал и временно придать другой, но с сохранением основного;
-
когда нужно наследовать поведение от множества классов и придать их одному объекту;
-
и др.
Паттерн «Декоратор» часто называют шаблоном «обертка». Второе название наиболее точно описывает действие паттерна, потому что при его использовании берется некий объект и «оборачивается» в совершенно новые свойства. По сути, получается новый объект на основе старого.
И новый, и старый объект имеют один интерфейс. Поэтому для конечного пользователя «обертывание» оставляет тот же принцип работы с объектом. Меняются только свойства, заложенные в «обертке».
Работу паттерна «Декоратор» можно проследить в жизни. Человек — это обычный объект. Одежда на человеке — это паттерн «Декоратор» или «обертка». Неважно, как одет человек, — он всегда остается одним и тем же человеком с основным набором свойств. Однако, когда человек выходит зимой на улицу — важно одеться, чтобы не замерзнуть. Если человек занимается спортом — важно, чтобы одежда соответствовала. Если у человека на работе дресс-код «строгий стиль», значит, он должен надеть «строгую» одежду, а не прийти в спортивном костюме. То есть разная одежда — это свойства разных классов: «зима», «спорт», «работа» и др.
Как реализовать паттерн «Декоратор»
Как реализовать паттерн «Декоратор» при помощи кода — мы покажем чуть ниже. Для реализации паттерна «Декоратор» нужно проделать определенную работу. Например:
-
Удостоверьтесь, что у вас есть основной объект для «обертывания» и дополнительные свойства, которые нужно ему придать.
-
Создайте единый интерфейс объекта без дополнительных свойств и с дополнительными свойствами.
-
Разработайте класс основного объекта, куда будет помещаться его основная бизнес-логика.
-
Разработайте основной класс паттерна «Декоратор». Такой класс имеет поля для хранения ссылок на дополнительные свойства других классов.
-
Основной объект и класс «Декоратор» должны управляться одним интерфейсом.
Паттерн «Декоратор»: преимущества, недостатки и сравнение с другими паттернами
Среди преимуществ паттерна «Декоратор» можно отметить:
-
высокую гибкость, по сравнению с наследованием;
-
динамическое придание свойств объекту;
-
множественное придание свойств одному объекту;
-
и др.
Среди недостатков можно отметить:
-
усложненную конфигурацию объектов при многократном «обертывании» одного и того же объекта;
-
множество небольших классов.
Паттерн «Декоратор», по сравнению с другими паттернами:
-
«Декоратор» улучшает основной объект дополнительными свойствами, не изменяя интерфейс. А паттерн «Адаптер» делает то же самое, но изменяет интерфейс основного объекта.
-
«Декоратор» улучшает прежний интерфейс. Паттерн «Адаптер» приносит альтернативный интерфейс. Паттерн «Заместитель» не трогает интерфейс вообще.
-
Паттерны «Декоратор» и «Цепочки обязанностей» очень похожи. Однако «Декоратор» не влияет на работу других «декораторов», а «Цепочка обязанностей» в любой момент может прервать свои «рабочие звенья».
-
Паттерны «Декоратор» и «Компоновщик» также похожи. Однако «Декоратор» воздействует только на один объект, а «Компоновщик» может сразу на несколько.
-
Паттерн «Декоратор» воздействует на объект «снаружи», а паттерн «Стратегия» воздействует на объект «изнутри».
Паттерн «Декоратор»: примеры реализации
Паттерн «Декоратор» применяется во многих языках программирования. Например:
- Kotlin;
- Ruby;
- Java;
- C#;
- C++;
- PHP;
- JavaScript;
- CoffeeScript;
- Swift;
- и др.
Приведем пример шаблона паттерна «Декоратор» на нескольких языках программирования.
Паттерн «Декоратор» на С++:
class Object {
public:
virtual ~Object() { }
virtual std::string Operation() const = 0;
} ;
class ConcreteObject : public Object {
public:
std::string Operation() const override {
return "ConcreteObject";
}
} ;
class Decoration : public Object {
protected:
Object* component_;
public:
Decoration(Object* component) : component_(component) {
}
std::string Operation() const override {
return this->component_->Operation();
}
} ;
class ConcreteDecorationA : public Decoration {
public:
ConcreteDecorationA(Object* component) : Decoration(component) {
}
std::string Operation() const override {
return "ConcreteDecorationA(" + Decoration::Operation() + ")";
}
} ;
class ConcreteDecorationB : public Decoration {
public:
ConcreteDecorationB(Object* component) : Decoration(component) {
}
std::string Operation() const override {
return "ConcreteDecorationB(" + Decoration::Operation() + ")";
}
} ;
void ClientCode(Object* component) {
// ...
std::cout << "RESULT: " << component->Operation();
// ...
}
int main() {
Object* simple = new ConcreteObject;
std::cout << "Client: Я назначаю простой объект :\n";
ClientCode(simple);
std::cout << "\n\n";
Object* decorator1 = new ConcreteDecorationA(simple);
Object* decorator2 = new ConcreteDecorationB(decoration1);
std::cout << "Client: Сейчас я назначаю декорированный объект:\n";
ClientCode(decoration2);
std::cout << "\n";
delete simple;
delete decoration1;
delete decoration2;
return 0;
}
Код на Ruby:
module DecorationPattern
class Object
def initialize(line)
@line = line
end
def write_line
@line
end
end
module Decoration
def initialize(object)
@object = object
end
def write_line
raise NotImplementedError
end
end
class Upcaser
include Decoration
def write_line
@object.write_line.upcase
end
end
class Timestamper
include Decoration
def write_line
"# { Time.now.strftime('%H:%m') } # { @object.write_line } "
end
end
class Datestamper
include Decoration
def write_line
"# { Time.now.strftime('%d.%m.%y') } # { @object.write_line } "
end
end
def self.run
puts '=> Decoration'
object = Object.new('Написан какой-то текст')
puts "Object:\n=> # { object.write_line } "
upcased = Upcaser.new(object)
puts "Upcased:\n=> # { upcased.write_line } "
timestamped = Timestamper.new(object)
puts "Timestamped:\n=> # { timestamped.write_line } "
datestamped = Datestamper.new(object)
puts "Datestamped:\n=> # { datestamped.write_line } "
upcased_timestamped = Timestamper.new(Upcaser.new(object))
puts "Upcased and timestamped:\n=> # { upcased_timestamped.write_line } "
upcased_datestamped_timestamped = Datestamper.new(Timestamper.new(Upcaser.new(object)))
puts "Upcased, datestamped and timestamped:\n=> # { upcased_datestamped_timestamped.write_line } "
datestamped_timestamped = Datestamper.new(Timestamper.new(object))
puts "Datestamped and timestamped:\n=> # { datestamped_timestamped.write_line } "
puts ''
end
end
DecorationPattern.run
Код на PHP:
<?php
interface MyText
{
public function show();
}
class TextPresentation implements MyText
{
protected $object;
public function __construct(MyText $text) {
$this->object = $text;
}
public function show() {
echo 'Привет';
$this->object->show();
}
}
class MyWorld implements MyText
{
protected $object;
public function __construct(MyText $text) {
$this->object = $text;
}
public function show() {
echo 'друзья';
$this->object->show();
}
}
class MySpace implements MyText
{
protected $object;
public function __construct(MyText $text) {
$this->object = $text;
}
public function show() {
echo ' ';
$this->object->show();
}
}
class MyEmpty implements MyText
{
public function show() {
}
}
$decorator = new TextPresentation(new MySpace(new MyWorld(new MyEmpty())));
$decorator->show(); // Привет друзья
echo '<br />' . PHP_EOL;
$decorator = new MyWorld(new MySpace(new TextPresentation(new MyEmpty())));
$decorator->show(); // друзья Привет
Заключение
Паттерн «Декоратор» — удобный инструмент, однако им нужно научиться пользоваться. Его удобно использовать в программах, когда объект один, а «обертка» будет разная. При этом от «обертки» зависит конечный продукт для клиента. В жизни это может быть:
-
заказ пиццы онлайн, когда от выбора начинки и количества ингредиентов зависит конечная стоимость пиццы для клиента;
-
выбор кофе онлайн, когда от сорта и объема кофе также будет зависеть его конечная стоимость;
-
выбор одежды, когда от материала изготовления, цвета и размера зависит стоимость продукта для клиента;
-
и др.