Постановка задачи
Нам нужно создать программное обеспечение для пиццерии, которая хочет готовить различные виды пиццы, например, «Куриная пицца», «Плоский хлеб», «Пепперони Пицца с добавлением сырного сыра», добавлять на нее начинки.
Попробуем выяснить, какой шаблон проектирования подходит для этой постановки задачи и по какому сценарию. Традиционно, для проблемы с пиццей, чаще всего используется шаблон строителя. Тем не менее, есть некоторые примеры использования декоратора, оба подхода верны, но есть разница в сценарии использования Builder — это шаблон создания объекта, тогда как decorator используется для изменения уже построенного объекта во время выполнения.
Давайте попробуем понять это на примерах:
-
Образец Строителя:
Здесь используется тот случай, когда пицца готовится за один раз с заданными характеристиками.
Давайте посмотрим класс пиццы:
01020304050607080910111213141516171819202122232425262728293031323334353637383940414243444546474849public
class
Pizza {
private
float
totalPrice =
0
;
private
Size size;
private
Topping topping;
private
Crust crust;
private
Cheese cheese;
public
Size getSize() {
return
size;
}
public
void
setSize(Size size) {
this
.size = size;
}
public
Topping getTopping() {
return
topping;
}
public
void
setTopping(Topping topping) {
this
.topping = topping;
}
public
Crust getCrust() {
return
crust;
}
public
void
setCrust(Crust crust) {
this
.crust = crust;
}
public
Cheese getCheese() {
return
cheese;
}
public
void
setCheese(Cheese cheese) {
this
.cheese = cheese;
}
public
float
getTotalPrice() {
return
totalPrice;
}
public
void
addToPrice(
float
price) {
this
.totalPrice = totalPrice + price;
}
}
4 класса enum:
0102030405060708091011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162public
enum
Cheese {
AMERICAN {
public
float
getCost() {
return
40
;
}
}, ITALIAN {
public
float
getCost() {
return
60
;
}
};
public
abstract
float
getCost();
}
public
enum
Crust {
THIN {
public
float
getCost(){
return
70
;
}
} , STUFFED{
public
float
getCost(){
return
90
;
}
};
public
abstract
float
getCost();
}
public
enum
Size {
MEDIUM {
public
float
getCost() {
return
100
;
}
}, LARGE {
public
float
getCost() {
return
160
;
}
};
public
abstract
float
getCost();
}
public
enum
Topping {
PEPPERONI {
public
float
getCost(){
return
30
;
}
}, CHICKEN{
public
float
getCost(){
return
35
;
}
}, MUSHROOM{
public
float
getCost(){
return
20
;
}
};
public
abstract
float
getCost();
}
Класс PizzaBuilder:
01020304050607080910111213141516171819202122232425262728293031public
class
PizzaBuilder {
Pizza pizza =
new
Pizza();
public
PizzaBuilder withTopping(Topping topping) {
pizza.setTopping(topping);
pizza.addToPrice(topping.getCost());
return
this
;
}
public
PizzaBuilder withSize(Size size) {
pizza.setSize(size);
pizza.addToPrice(size.getCost());
return
this
;
}
public
PizzaBuilder withCrust(Crust crust) {
pizza.setCrust(crust);
pizza.addToPrice(crust.getCost());
return
this
;
}
public
Pizza build() {
return
pizza;
}
public
double
calculatePrice() {
return
pizza.getTotalPrice();
}
}
Тестовый пример:
0102030405060708091011public
class
PizzaBuilderTest {
@Test
public
void
shouldBuildThinCrustChickenPizza(){
Pizza pizza =
new
PizzaBuilder().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 (оформленный).
Интерфейс пиццы:
1234public
interface
Pizza {
public
String bakePizza();
public
float
getCost();
}
Реализация базовой пиццы:
123456789public
class
BasePizza
implements
Pizza{
public
String bakePizza() {
return
"Basic Pizza";
}
public
float
getCost(){
return
100
;
}
}
PizzaDecorator класс:
010203040506070809101112131415public
class
PizzaDecorator
implements
Pizza {
Pizza pizza;
public
PizzaDecorator(Pizza newPizza) {
this
.pizza = newPizza;
}
public
String bakePizza() {
return
pizza.bakePizza();
}
@Override
public
float
getCost() {
return
pizza.getCost();
}
}
2 декоратора: гриб и пепперони
01020304050607080910111213141516public
class
Mushroom
extends
PizzaDecorator {
public
Mushroom(Pizza newPizza) {
super
(newPizza);
}
@Override
public
String bakePizza() {
return
super
.bakePizza() + " with Mashroom Topings";
}
@Override
public
float
getCost() {
return
super
.getCost()+
80
;
}
}
01020304050607080910111213141516public
class
Pepperoni
extends
PizzaDecorator {
public
Pepperoni(Pizza newPizza) {
super
(newPizza);
}
@Override
public
String bakePizza() {
return
super
.bakePizza() + " with Pepperoni Toppings";
}
@Override
public
float
getCost() {
return
super
.getCost()+
110
;
}
}
Тестовый кейс:
123456789public
class
PizzaDecoratorTest {
@Test
public
void
shouldMakePepperoniPizza(){
Pizza pizza =
new
Pepperoni(
new
BasePizza());
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 Анируд Бхатнагар в блоге Анирудх Бхатнагар. |