Когда использовать шаблон декоратора?
Я перебираю свои шаблоны проектирования, и один шаблон, который мне еще предстоит серьезно использовать в моем кодировании, - это шаблон Decorator.
Я понимаю шаблон, но я хотел бы знать несколько хороших конкретных примеров того, как шаблон декоратора является лучшим / оптимальным / элегантным решением. Конкретные ситуации, когда необходимость в шаблоне декоратора действительно удобна.
Благодарю.
11 ответов
Шаблон декоратора часто используется с потоками: вы можете обернуть поток потоком, чтобы получить дополнительную функциональность. Я видел это с.Net Framework - насколько я знаю, это происходит в другом месте. Мое любимое использование GZipStream вокруг FileStream для дополнительного сжатия.
Шаблон Decorator используется для добавления дополнительных функций к определенному объекту, а не к классу объектов. Легко добавить функциональность ко всему классу объектов, создав подклассы объекта, но невозможно расширить один объект таким способом. С помощью Pattern Decorator вы можете добавить функциональность к одному объекту и оставить другие, как он, без изменений.
В Java классическим примером шаблона декоратора является реализация потоков ввода-вывода Java.
FileReader frdr = new FileReader(filename);
LineNumberReader lrdr = new LineNumberReader(frdr);
Предыдущий код создает читателя - lrdr
- который читает из файла и отслеживает номера строк. Строка 1 создает программу чтения файлов (frdr
), а строка 2 добавляет отслеживание номера строки.
На самом деле, я настоятельно рекомендую вам взглянуть на исходный код Java для классов ввода-вывода Java.
Недавно я использовал шаблон декоратора в веб-сервисе, который использует следующий интерфейс CommandProcessor:
public Command receive(Request request);
public Response execute(Command command);
public void respond(Response response);
По сути, CommandProcessor получает запрос и создает соответствующую команду, выполняет команду и создает соответствующий ответ и отправляет ответ. Когда я захотел добавить время и записать его, я создал TimerDecorator, который использовал существующий CommandProcessor в качестве своего компонента. TimerDecorator реализует интерфейс CommandProcessor, но просто добавляет время и затем вызывает свою цель, которая является настоящим CommandProcessor. Что-то вроде этого:
public class TimerDecorator implements CommandProcessor {
private CommandProcessor target;
private Timer timer;
public TimerDecorator(CommandProcessor processor) {
this.target = processor;
this.timer = new Timer();
}
public Command receive(Request request) {
this.timer.start();
return this.target.receive(request);
}
public Response execute(Command command) {
return this.target.execute(command);
}
public void respond(Response response) {
this.target.response(response);
this.timer.stop();
// log timer
}
}
Таким образом, настоящий CommandProcessor обернут внутри TimerDecorator, и я могу рассматривать TimerDecorator точно так же, как целевой CommandProcessor, но теперь добавлена логика синхронизации.
Шаблон Decorator динамически изменяет функциональность объекта во время выполнения, не влияя на существующую функциональность объектов.
Ключевые варианты использования:
- Добавляйте дополнительные функции / обязанности динамически
- Удалить функции / обязанности динамически
- Избегайте слишком большого количества подклассов, чтобы добавить дополнительные обязанности.
Недостатки:
- Чрезмерное использование принципа Open Closed ( Open для расширения и Closed для модификации). Используйте эту функцию экономно, когда код наименее вероятно изменился.
- Слишком много маленьких классов и добавит накладные расходы на обслуживание.
Пример из реальной жизни: вычислите цену напитка, который может содержать несколько вкусов.
abstract class Beverage {
protected String name;
protected int price;
public Beverage(){
}
public Beverage(String name){
this.name = name;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
protected void setPrice(int price){
this.price = price;
}
protected int getPrice(){
return price;
}
protected abstract void decorateBeverage();
}
class Tea extends Beverage{
public Tea(String name){
super(name);
setPrice(10);
}
public void decorateBeverage(){
System.out.println("Cost of:"+ name +":"+ price);
// You can add some more functionality
}
}
class Coffee extends Beverage{
public Coffee(String name){
super(name);
setPrice(15);
}
public void decorateBeverage(){
System.out.println("Cost of:"+ name +":"+ price);
// You can add some more functionality
}
}
abstract class BeverageDecorator extends Beverage {
protected Beverage beverage;
public BeverageDecorator(Beverage beverage){
this.beverage = beverage;
setName(beverage.getName()+"+"+getDecoratedName());
setPrice(beverage.getPrice()+getIncrementPrice());
}
public void decorateBeverage(){
beverage.decorateBeverage();
System.out.println("Cost of:"+getName()+":"+getPrice());
}
public abstract int getIncrementPrice();
public abstract String getDecoratedName();
}
class SugarDecorator extends BeverageDecorator{
public SugarDecorator(Beverage beverage){
super(beverage);
}
public void decorateBeverage(){
super.decorateBeverage();
decorateSugar();
}
public void decorateSugar(){
System.out.println("Added Sugar to:"+beverage.getName());
}
public int getIncrementPrice(){
return 5;
}
public String getDecoratedName(){
return "Sugar";
}
}
class LemonDecorator extends BeverageDecorator{
public LemonDecorator(Beverage beverage){
super(beverage);
}
public void decorateBeverage(){
super.decorateBeverage();
decorateLemon();
}
public void decorateLemon(){
System.out.println("Added Lemon to:"+beverage.getName());
}
public int getIncrementPrice(){
return 3;
}
public String getDecoratedName(){
return "Lemon";
}
}
public class VendingMachineDecorator {
public static void main(String args[]){
Beverage beverage = new SugarDecorator(new LemonDecorator(new Tea("Assam Tea")));
beverage.decorateBeverage();
beverage = new SugarDecorator(new LemonDecorator(new Coffee("Cappuccino")));
beverage.decorateBeverage();
}
}
выход:
Cost of:Assam Tea:10
Cost of:Assam Tea+Lemon:13
Added Lemon to:Assam Tea
Cost of:Assam Tea+Lemon+Sugar:18
Added Sugar to:Assam Tea+Lemon
Cost of:Cappuccino:15
Cost of:Cappuccino+Lemon:18
Added Lemon to:Cappuccino
Cost of:Cappuccino+Lemon+Sugar:23
Added Sugar to:Cappuccino+Lemon
В этом примере вычисляется стоимость напитка в торговом автомате после добавления в напиток множества вкусов.
В приведенном выше примере:
Стоимость чая = 10, лимона = 3 и сахара = 5. Если вы производите сахар + лимон + чай, это стоит 18.
Стоимость кофе =15, лимон = 3 и сахар = 5. Если вы делаете сахар + лимон + кофе, это стоит 23
Благодаря использованию одного и того же декоратора для обоих напитков (чай и кофе) количество подклассов было сокращено. В отсутствие шаблона Decorator у вас должны быть разные подклассы для разных комбинаций.
Комбинации будут такими:
SugarLemonTea
SugarTea
LemonTea
SugarLemonCapaccuino
SugarCapaccuino
LemonCapaccuino
и т.п.
Благодаря использованию одного и того же декоратора для обоих напитков количество подклассов было сокращено. Это возможно благодаря композиции, а не концепции наследования, используемой в этом шаблоне.
Связанный вопрос SE:
Полезные ссылки:
дизайн-шаблоны-декоратор от dzone
декоратор путем создания источника
статья о дизайне
Декоратор прост, но чрезвычайно мощный. Это ключ к достижению разделения интересов и важный инструмент для открытого закрытого принципа. Возьмите общий пример размещения заказа на товар:
IOrderGateway
{
void PlaceOrder(Order order);
{
Основная реализация: AmazonAffiliateOrderGateway
Возможные декораторы могут быть:
IncrementPerformanceCounterOrderGateway
QueueOrderForLaterOnTimeoutOrderGateway
EmailOnExceptionOrderGateway
InterceptTestOrderAndLogOrderGateway
Посмотрите здесь для более подробного примера.
- динамически и прозрачно добавлять обязанности к отдельным объектам.
- для обязанностей, которые могут быть сняты.
- когда расширение подклассами нецелесообразно. Иногда возможно большое количество независимых расширений, что может привести к взрыву подклассов для поддержки каждой комбинации.
Zend Framework использует декоратор для элементов формы
Еще немного информации: http://framework.zend.com/manual/en/zend.form.decorators.html
Определение ГОФ :
Динамически прикрепляйте дополнительные обязанности к объекту. Декораторы предоставляют гибкую альтернативу подклассам для расширения функциональности.
Этот шаблон говорит, что класс должен быть закрыт для модификации, но открыт для расширения, новая функциональность может быть добавлена без нарушения существующих функций. Эта концепция очень полезна, когда мы хотим добавить специальные функции к конкретному объекту, а не ко всему классу. В этом шаблоне мы используем концепцию композиции объектов вместо наследования.
Общий пример:
public abstract class Decorator<T> {
private T t;
public void setTheKernel(Supplier<? extends T> supplier) {
this.t = supplier.get();
}
public T decorate() {
return Objects.isNull(t) ? null : this.t;
}
}
Реализация
public interface Repository {
void save();
}
public class RepositoryImpl implements Repository{
@Override
public void save() {
System.out.println("saved successfully");
}
}
public class EnhancedRepository<T> extends Decorator<T> {
public void enhancedSave() {
System.out.println("enhanced save activated");
}
}
public class Main {
public static void main(String[] args) {
EnhancedRepository<Repository> enhanced = new EnhancedRepository<>();
enhanced.setTheKernel(RepositoryImpl::new);
enhanced.enhancedSave();
enhanced.decorate().save();
}
}
Шаблон декоратора используется самим языком C#. Он используется для украшения класса потокового ввода-вывода C#. Декорированные версии классифицируются как BufferedStream, FileStrem, MemoryStrem, NetworkStream и CryptoStream.
Эти подклассы наследуются от класса Stream, а также содержат экземпляр класса Stream.
Узнайте больше здесь
Если вы хотите добавить несколько функций к классу / объекту и хотите иметь гибкость, чтобы добавлять их в любое время, вам пригодится Decorator. вы можете расширить базовый класс и добавить желаемые изменения, но таким образом вы получите множество подклассов, которые могут поразить вас. но с декоратором вы можете вносить желаемые изменения, но при этом иметь простой, понятный процесс. ваш дизайн легко открыть для расширения, но близок к модификации с действительно простой логикой. Лучшим примером могут быть объекты Stream, реализованные на Java и C#. например, у вас есть файловый поток, и в одном варианте использования вы хотите его зашифровать, затем заархивировать, затем записать в журнал и, наконец, сделать с ним что-нибудь интересное. затем в другом классе вы решаете сделать что-то еще. Вы хотите преобразовать это, затем зашифровать это, затем получить время, блух, блух, блух.снова у вас есть другой поток в других классах. если вы хотите использовать наследование, вам нужно создать как минимум 3 подкласса, и если требуется какое-либо другое требование, вам нужно добавить больше подклассов, которые в этом случае (Stream) вы получите десятки подклассов для небольших изменений.
class EncryptZipLogStream{}
class ConvertEncryptTimeStream{}
class MoreStreamProcess{}
class OtherMoreStreamProcess{}
...
И в каждом случае использования вы должны помнить, какой класс нужен, и пытаться его использовать. Но представьте, что вы использовали композицию, а не наследование, и у вас есть классы Decorator для каждого процесса Stream, вы можете легко комбинировать нужные оболочки и получать любые желаемые процессы с наименьшими усилиями и максимальной простотой.
class WhereIWannaUseUseCaseOne {
EncryptStream(ZipStream(LogStream(FileStream("file name)))));
// rest of the code to use the combines streams.
}
тогда вы придумали другой вариант использования:
class WhereIWannaUseUseCaseSecond {
ConvertStream(TimeStream(LogStream(FileStream("file name)))));
// rest of the code to use the combines streams.
}
И так далее и тому подобное, у вас есть возможность делать все, что вы хотите, во время выполнения с простым потоком и понятной логикой.
Очень реальный пример для меня:
Мне пришлось обновить класс, который активно использовался в проекте, но этот класс находился в библиотеке, а исходный код был утерян в преисподней.
Я мог либо декомпилировать всю библиотеку, чтобы создать другую ее версию, либо использовать шаблон проектирования декоратора, что я и сделал. Это позволило мне добавить недостающие функции и просто обновить конфигурацию.
При ответственном использовании это очень полезный шаблон.
Этот конкретный случай вдохновил меня на создание этого видео, в котором я объясняю узор.