Постановка задачи
Нам нужно создать программное обеспечение для пиццерии, которая хочет готовить различные виды пиццы, например, «Куриная пицца», «Плоский хлеб», «Пепперони Пицца с добавлением сырного сыра», добавлять на нее начинки.
Попробуем выяснить, какой шаблон проектирования подходит для этой постановки задачи и по какому сценарию. Традиционно, для проблемы с пиццей, чаще всего используется шаблон строителя. Тем не менее, есть некоторые примеры использования декоратора, оба подхода верны, но есть разница в сценарии использования Builder — это шаблон создания объекта, тогда как decorator используется для изменения уже построенного объекта во время выполнения.
Давайте попробуем понять это на примерах:
-
Образец Строителя:
Здесь используется тот случай, когда пицца готовится за один раз с заданными характеристиками.
Давайте посмотрим класс пиццы:
01020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849publicclassPizza {privatefloattotalPrice =0;privateSize size;privateTopping topping;privateCrust crust;privateCheese cheese;publicSize getSize() {returnsize;}publicvoidsetSize(Size size) {this.size = size;}publicTopping getTopping() {returntopping;}publicvoidsetTopping(Topping topping) {this.topping = topping;}publicCrust getCrust() {returncrust;}publicvoidsetCrust(Crust crust) {this.crust = crust;}publicCheese getCheese() {returncheese;}publicvoidsetCheese(Cheese cheese) {this.cheese = cheese;}publicfloatgetTotalPrice() {returntotalPrice;}publicvoidaddToPrice(floatprice) {this.totalPrice = totalPrice + price;}}4 класса enum:
0102030405060708091011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162publicenumCheese {AMERICAN {publicfloatgetCost() {return40;}}, ITALIAN {publicfloatgetCost() {return60;}};publicabstractfloatgetCost();}publicenumCrust {THIN {publicfloatgetCost(){return70;}} , STUFFED{publicfloatgetCost(){return90;}};publicabstractfloatgetCost();}publicenumSize {MEDIUM {publicfloatgetCost() {return100;}}, LARGE {publicfloatgetCost() {return160;}};publicabstractfloatgetCost();}publicenumTopping {PEPPERONI {publicfloatgetCost(){return30;}}, CHICKEN{publicfloatgetCost(){return35;}}, MUSHROOM{publicfloatgetCost(){return20;}};publicabstractfloatgetCost();}Класс PizzaBuilder:
01020304050607080910111213141516171819202122232425262728293031publicclassPizzaBuilder {Pizza pizza =newPizza();publicPizzaBuilder withTopping(Topping topping) {pizza.setTopping(topping);pizza.addToPrice(topping.getCost());returnthis;}publicPizzaBuilder withSize(Size size) {pizza.setSize(size);pizza.addToPrice(size.getCost());returnthis;}publicPizzaBuilder withCrust(Crust crust) {pizza.setCrust(crust);pizza.addToPrice(crust.getCost());returnthis;}publicPizza build() {returnpizza;}publicdoublecalculatePrice() {returnpizza.getTotalPrice();}}Тестовый пример:
0102030405060708091011publicclassPizzaBuilderTest {@TestpublicvoidshouldBuildThinCrustChickenPizza(){Pizza pizza =newPizzaBuilder().withCrust(Crust.THIN).withTopping(Topping.CHICKEN).withSize(Size.LARGE).build();assertEquals(Topping.CHICKEN,pizza.getTopping());assertEquals(Size.LARGE,pizza.getSize());assertEquals(Crust.THIN,pizza.getCrust());assertEquals(265.0,pizza.getTotalPrice(),0);}} -
Образец Декоратора:
Шаблон «Декоратор» используется для динамического добавления или удаления дополнительных функций или обязанностей из объекта без воздействия на исходный объект. Вариант использования: сначала готовится базовая пицца, а затем к ней добавляются различные характеристики.
Здесь нам нужен интерфейс (Pizza) для BasicPizza (Конкретный компонент), который мы хотим декорировать, и класс PizzaDecorator, который содержит поле ссылки интерфейса Pizza (оформленный).
Интерфейс пиццы:
1234publicinterfacePizza {publicString bakePizza();publicfloatgetCost();}Реализация базовой пиццы:
123456789publicclassBasePizzaimplementsPizza{publicString bakePizza() {return"Basic Pizza";}publicfloatgetCost(){return100;}}PizzaDecorator класс:
010203040506070809101112131415publicclassPizzaDecoratorimplementsPizza {Pizza pizza;publicPizzaDecorator(Pizza newPizza) {this.pizza = newPizza;}publicString bakePizza() {returnpizza.bakePizza();}@OverridepublicfloatgetCost() {returnpizza.getCost();}}2 декоратора: гриб и пепперони
01020304050607080910111213141516publicclassMushroomextendsPizzaDecorator {publicMushroom(Pizza newPizza) {super(newPizza);}@OverridepublicString bakePizza() {returnsuper.bakePizza() + " with Mashroom Topings";}@OverridepublicfloatgetCost() {returnsuper.getCost()+80;}}01020304050607080910111213141516publicclassPepperoniextendsPizzaDecorator {publicPepperoni(Pizza newPizza) {super(newPizza);}@OverridepublicString bakePizza() {returnsuper.bakePizza() + " with Pepperoni Toppings";}@OverridepublicfloatgetCost() {returnsuper.getCost()+110;}}Тестовый кейс:
123456789publicclassPizzaDecoratorTest {@TestpublicvoidshouldMakePepperoniPizza(){Pizza pizza =newPepperoni(newBasePizza());assertEquals("Basic Pizza with Pepperoni Toppings",pizza.bakePizza());assertEquals(210.0,pizza.getCost(),0);}}
Разница
При создании объектов используются такие шаблоны, как строитель и фабрика (и абстрактная фабрика). А шаблоны, такие как декоратор (также называемые шаблонами структурного проектирования), используются для расширяемости или для обеспечения структурных изменений уже созданных объектов.
Оба типа шаблонов в значительной степени предпочитают композицию, а не наследование, поэтому использование этого в качестве дифференциатора для использования компоновщика вместо декоратора не имеет смысла. Оба дают поведение во время выполнения, а не наследуют его.
Таким образом, следует использовать строитель, если он хочет ограничить создание объекта определенными свойствами / возможностями. Например, есть 4-5 атрибутов, которые необходимо установить до создания объекта, или мы хотим заморозить создание объекта, пока определенные атрибуты еще не установлены. По сути, используйте его вместо конструктора — как утверждает Джошуа Блох в Effective Java, 2nd Edition . Строитель предоставляет атрибуты, которые должен иметь сгенерированный объект, но скрывает, как их устанавливать.
Декоратор используется для добавления новых возможностей существующего объекта для создания нового объекта. Нет ограничений на замораживание объекта, пока не будут добавлены все его функции. Оба используют композицию, поэтому они могут выглядеть одинаково, но они сильно различаются по своему сценарию использования и намерению.
Другим подходом может быть использование Factory Pattern; если мы не хотим раскрывать атрибуты и хотим, чтобы создание «пиццы» было «волшебным» внутри, тогда на основе некоторых атрибутов. Мы рассмотрим эту реализацию с использованием Factory Pattern в следующем посте.
| Ссылка: | Проблема пиццы — строитель против декоратора от нашего партнера JCG Анируд Бхатнагар в блоге Анирудх Бхатнагар. |