Если классы находятся на стадии проектирования, будет несложно добавить декораторы. Однако если необходимо снабдить декоратором существующую систему, вам может понадобиться переделать некоторые классы. Например, целевой класс должен реализовывать тот же интерфейс, что реализует декоратор.
Эта статья демонстрирует применение паттерна «Декоратор» при разработке упрощенной РОS-системы для пиццерии. В данной статье сможете прочитать где использовать паттерн декоратор. Каждая пицца может быть украшена дополнительными начинками, такими как двойной сыр и бесплатный чили.
Во-первых, вам предстоит создать интерфейс Order, реализуемый с помощью класса Pizza и абстрактного класса декоратора Extra. Класс Extra расширяется классами добавочных начинок: DoubleExtra, NoCostExtra и RegularExtra.
Начнем с создании интерфейса Order:
1 2 3 4 5 6 |
public interface Order { public double getPrice(); public String getLabel(); } |
Далее создадим класс, представляющий в меню пиццу(«Четыре сезона», «Маргарита», «Гавайская» и т.д.). Это целевой объект для декорирования. Класс будет реализовать интерфейс Order.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public class Pizza implements Order { private String label; private double price; public Pizza(String label, double price) { this.label = label; this.price = price; } public double getPrice() { return this.price; } public String getLabel() { return this.label; } } |
Следующий код создает пиццу «Четыре сезона«.
1 |
Order fourSeasonsPizza = new Pizza("Четыре сезона", 10); |
Далее нам необходимо создать декораторы, которые будут «украшать» пиццу добавочными начинками. Используем абстрактный класс таким образом, чтобы конкретным классам не пришлось реализовать все бизнес-методы интерфейса. Абстрактный декоратор создаст шаблон, который смогут расширять другие декораторы.
Пусть у вас есть различные типы начинок (сыр, перец чили, ананас и т. д.). Представьте, что посетитель хочет заказать немного более острое блюдо и ресторан не берет отдельную плату за эту дополнительную начинку.
Таким образом, вам нужен декоратор, не добавляющий ничего к цене пиццы, но обеспечивающий соответствующую маркировку ( что был заказан дополнительный чили ).
Кроме того,посетитель может попросить две дополнительные порции сыра и, если система напечатает «сыр» дважды, шеф-повар может подумать, что это ошибка в программе, и добавить только одну порцию сыра.
Поэтому вам нужен дополнительный конкретный декоратор, чтобы выполнять правильную маркировку двойных начинок. Эти цели достигаются с помощью абстрактного декоратора, добавляющий дополнительные начинки.
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 |
public abstract class Extra implements Order { protected Order order; protected String label; protected double price; public Extra(String label, double price, Order order) { this.label = label; this.price = price; this.order = order; } // Цена может оказаться основной проблемой. // так что поручим это конкретной реализации public abstract double getPrice(); // Стандартной маркировки должно быть достаточно public String getLabel() { return order.getLabel() + ", " + this.label; } } |
Теперь, когда у вас есть абстрактный декоратор, вы можете добавлять конкретное поведение и создавать конкретные декораторы. Вы начнете с декоратора RegularExtra, добавляющего цену и этикетку к целевому объекту (пицце).
Поскольку функция маркировки уже есть в абстрактном декораторе и наследуется всеми расширяющими его подклассами, вам только нужно реализовать функциональность формирования цены. Следующие строки кода об этом позаботятся.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class RegularExtra extends Extra { public RegularExtra(String label, double price, Order order) { super(label, price, order); } public Double getPrice() { return this.price + order.getPrice(); } } |
Далее нам нужно создать NoCostDecorator, который меняет строку label, но не добавляет ничего к цене пиццы. Следующий декоратор добавляет дополнительные начинки бесплатно.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public class NoCostExtra extends Extra { public NoCostExtra(String label, double price, Order order) { super(label, price, order); } public Double getPrice() { return order.getPrice(); } } |
Дальше создаем наш последний декоратор DoubleExtra, чтобы избежать двукратной печати начинки на этикетке. Он удваивает цену и добавляет ключевое слово double перед целевой этикеткой.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class DoubleExtra extends Extra { public DoubleExtra(String label, double price, Order order) { super(label, price, order); } public Double getPrice() { return (this.price * 2) + order.getPrice(); } public String getLabel() { return order.getLabel() + ", Double " + this.label; } } |
Теперь, когда паттерн «Декоратор» для добавления дополнительных начинок
к вашей пицце реализован, можете протестировать реализацию.
1 2 3 4 5 6 7 |
Order fourSeasonsPizza = new Pizza("Four Seasons Pizza", 10); fourSeasonsPizza = new RegularExtra("Pepperoni", 4, fourSeasonsPizza); fourSeasonsPizza = new DoubleExtra("Mozzarella", 2, fourSeasonsPizza); fourSeasonsPizza = new NoCostExtra("Chili", 2, fourSeasonsPizza); System.out.println(fourSeasonsPizza.getPrice()); System.out.println(fourSeasonsPizza.getLabel()); |
Вывод в консоли будет следующим:
1 2 |
18.0 Pizza, Pepperoni, Double Mozzarella, Chili |
Но погодите! Здесь есть потенциальная ошибка! Чили не бесплатен, если вы
заказываете его как гарнир, но шеф-повар с радостью подаст его бесплатно в пицце. Вам нужно позаботиться, чтобы система учитывала это различие. Только представьте себе, что эти значения и этикетки берутся из базы данных.
Что бы вы сделали для создания различных видов поведения для чили? Одним вариантом могло бы быть создание двух объектов для чили: один был бы маркирован как «с пиццей».
Конечно, это была бы халтура, оставляющая любому официанту лазейку для заказа бесплатного чили для его друзей. Другим вариантом стало бы создание дополнительного конструктора в абстрактном классе, не принимающего цену в качестве параметра. Любой не взимающий плату за добавки декоратор мог бы реализовать это.