Декоратор — это специалист по художественному оформлению интерьера и экстерьера. Он делает все красивым: квартиру, торговый центр, личный дворик, большие ландшафты и др. При чем здесь Декоратор и программирование? — спросите вы.
В программировании также есть собственный «специалист по декору» — это паттерн «Декоратор». Паттерн «Декоратор» — это структурный шаблон в программировании, который открывает возможность подключать дополнительную функциональность к объектам в динамическом режиме. Паттерн «Декоратор» свойственен многим языкам программирования. По своей концепции он чем-то напоминает наследование свойств или создание подклассов, однако предлагает более гибкие возможности определения дополнительных характеристик объектам.
Паттерн «Декоратор»
Паттерн «Декоратор» применяется в том случае, когда объекту необходимо придать какие-то дополнительные свойства в динамическом режиме. Но когда потребность в этих свойствах исчезнет, они должны быть сняты с объекта. Также он применяется в том случае, когда невозможно применить наследование. Например:
когда объекту нужно придать множество разных свойств, а для каждого свойства нужно определить собственный класс, таким образом, количество классов очень сильно «раздуется»;
когда с объекта нужно «снять» весь основной функционал и временно придать другой, но с сохранением основного;
когда нужно наследовать поведение от множества классов и придать их одному объекту;
и др.
Паттерн «Декоратор» часто называют шаблоном «обертка». Второе название наиболее точно описывает действие паттерна, потому что при его использовании берется некий объект и «оборачивается» в совершенно новые свойства. По сути, получается новый объект на основе старого.
И новый, и старый объект имеют один интерфейс. Поэтому для конечного пользователя «обертывание» оставляет тот же принцип работы с объектом. Меняются только свойства, заложенные в «обертке».
Работу паттерна «Декоратор» можно проследить в жизни. Человек — это обычный объект. Одежда на человеке — это паттерн «Декоратор» или «обертка». Неважно, как одет человек, — он всегда остается одним и тем же человеком с основным набором свойств. Однако, когда человек выходит зимой на улицу — важно одеться, чтобы не замерзнуть. Если человек занимается спортом — важно, чтобы одежда соответствовала. Если у человека на работе дресс-код «строгий стиль», значит, он должен надеть «строгую» одежду, а не прийти в спортивном костюме. То есть разная одежда — это свойства разных классов: «зима», «спорт», «работа» и др.
Как реализовать паттерн «Декоратор»
Как реализовать паттерн «Декоратор» при помощи кода — мы покажем чуть ниже. Для реализации паттерна «Декоратор» нужно проделать определенную работу. Например:
Удостоверьтесь, что у вас есть основной объект для «обертывания» и дополнительные свойства, которые нужно ему придать.
Создайте единый интерфейс объекта без дополнительных свойств и с дополнительными свойствами.
Разработайте класс основного объекта, куда будет помещаться его основная бизнес-логика.
Разработайте основной класс паттерна «Декоратор». Такой класс имеет поля для хранения ссылок на дополнительные свойства других классов.
Основной объект и класс «Декоратор» должны управляться одним интерфейсом.
Паттерн «Декоратор»: преимущества, недостатки и сравнение с другими паттернами
Среди преимуществ паттерна «Декоратор» можно отметить:
высокую гибкость, по сравнению с наследованием;
динамическое придание свойств объекту;
множественное придание свойств одному объекту;
и др.
Среди недостатков можно отметить:
усложненную конфигурацию объектов при многократном «обертывании» одного и того же объекта;
множество небольших классов.
Паттерн «Декоратор», по сравнению с другими паттернами:
«Декоратор» улучшает основной объект дополнительными свойствами, не изменяя интерфейс. А паттерн «Адаптер» делает то же самое, но изменяет интерфейс основного объекта.
«Декоратор» улучшает прежний интерфейс. Паттерн «Адаптер» приносит альтернативный интерфейс. Паттерн «Заместитель» не трогает интерфейс вообще.
Паттерны «Декоратор» и «Цепочки обязанностей» очень похожи. Однако «Декоратор» не влияет на работу других «декораторов», а «Цепочка обязанностей» в любой момент может прервать свои «рабочие звенья».
Паттерны «Декоратор» и «Компоновщик» также похожи. Однако «Декоратор» воздействует только на один объект, а «Компоновщик» может сразу на несколько.
Паттерн «Декоратор» воздействует на объект «снаружи», а паттерн «Стратегия» воздействует на объект «изнутри».
Паттерн «Декоратор»: примеры реализации
Паттерн «Декоратор» применяется во многих языках программирования. Например:
- 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(); // друзья Привет
Заключение
Паттерн «Декоратор» — удобный инструмент, однако им нужно научиться пользоваться. Его удобно использовать в программах, когда объект один, а «обертка» будет разная. При этом от «обертки» зависит конечный продукт для клиента. В жизни это может быть:
заказ пиццы онлайн, когда от выбора начинки и количества ингредиентов зависит конечная стоимость пиццы для клиента;
выбор кофе онлайн, когда от сорта и объема кофе также будет зависеть его конечная стоимость;
выбор одежды, когда от материала изготовления, цвета и размера зависит стоимость продукта для клиента;
и др.
Другое