[Программирование, Java, Проектирование и рефакторинг] Паттерн проектирования Builder (Строитель) в Java (перевод)
Автор
Сообщение
news_bot ®
Стаж: 7 лет 2 месяца
Сообщений: 27286
В преддверии скорого старта курса «Архитектура и шаблоны проектирования» делимся с вами переводом материала.
Приглашаем также всех желающих на открытый демо-урок «Шаблоны GRASP». На этом занятии мы проанализируем функциональное разделение функционала и рассмотрим 9 шаблонов GRASP. Присоединяйтесь!

А вот и я со своей очередной статьей о паттернах проектирования, а именно о паттерне проектирования Builder (он же Строитель). Очень полезный паттерн проектирования, который позволяет нам шаг за шагом конструировать сложные объекты.Паттерн проектирования Builder
- Паттерн проектирования Builder разработан для обеспечения гибкого решения различных задач создания объектов в объектно-ориентированном программировании.
- Паттерн проектирования Builder позволяет отделить построение сложного объекта от его представления.
- Паттерн Builder создает сложные объекты, используя простые объекты и поэтапный подход.
- Паттерн предоставляет один из лучших способов создания сложных объектов.
- Это один из паттернов проектирования банды четырех (GoF), которые описывают, как решать периодически возникающие задачи проектирования в объектно-ориентированном программном обеспечении.
- Этот паттерн полезен для создания разных иммутабельных объектов с помощью одного и того же процесса построения объекта.
Паттерн Builder — это паттерн проектирования, который позволяет поэтапно создавать сложные объекты с помощью четко определенной последовательности действий. Строительство контролируется объектом-распорядителем (director), которому нужно знать только тип создаваемого объекта.
Итак, паттерн проектирования Builder можно разбить на следующие важные компоненты:
- Product (продукт) - Класс, который определяет сложный объект, который мы пытаемся шаг за шагом сконструировать, используя простые объекты.
- Builder (строитель) - абстрактный класс/интерфейс, который определяет все этапы, необходимые для производства сложного объекта-продукта. Как правило, здесь объявляются (абстрактно) все этапы (buildPart), а их реализация относится к классам конкретных строителей (ConcreteBuilder).
- ConcreteBuilder (конкретный строитель) - класс-строитель, который предоставляет фактический код для создания объекта-продукта. У нас может быть несколько разных ConcreteBuilder-классов, каждый из которых реализует различную разновидность или способ создания объекта-продукта.
- Director (распорядитель) - супервизионный класс, под конролем котрого строитель выполняет скоординированные этапы для создания объекта-продукта. Распорядитель обычно получает на вход строителя с этапами на выполнение в четком порядке для построения объекта-продукта.
Паттерн проектирования Builder решает такие проблемы, как:
- Как класс (тот же самый процесс строительства) может создавать различные представления сложного объекта?
- Как можно упростить класс, занимающийся созданием сложного объекта?
Давайте реализуем пример со сборкой автомобилей, используя паттерн проектирования Builder.Пример со сборкой автомобилей с использованием паттерна проектирования BuilderШаг 1: Создайте класс Car (автомобиль), который в нашем примере является продуктом:
package org.trishinfotech.builder;
public class Car {
private String chassis;
private String body;
private String paint;
private String interior;
public Car() {
super();
}
public Car(String chassis, String body, String paint, String interior) {
this();
this.chassis = chassis;
this.body = body;
this.paint = paint;
this.interior = interior;
}
public String getChassis() {
return chassis;
}
public void setChassis(String chassis) {
this.chassis = chassis;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getPaint() {
return paint;
}
public void setPaint(String paint) {
this.paint = paint;
}
public String getInterior() {
return interior;
}
public void setInterior(String interior) {
this.interior = interior;
}
public boolean doQualityCheck() {
return (chassis != null && !chassis.trim().isEmpty()) && (body != null && !body.trim().isEmpty())
&& (paint != null && !paint.trim().isEmpty()) && (interior != null && !interior.trim().isEmpty());
}
@Override
public String toString() {
// StringBuilder class also uses Builder Design Pattern with implementation of java.lang.Appendable interface
StringBuilder builder = new StringBuilder();
builder.append("Car [chassis=").append(chassis).append(", body=").append(body).append(", paint=").append(paint)
return builder.toString();
}
}
Обратите внимание, что я добавил в класс проверочный метод doQualityCheck. Я считаю, что Builder не должен создавать неполные или невалидные Product-объекты. Таким образом, этот метод поможет нам в проверке сборки автомобилей.Шаг 2: Создайте абстрактный класс/интерфейс CarBuilder, в котором определите все необходимые шаги для создания автомобиля.
package org.trishinfotech.builder;
public interface CarBuilder {
// Этап 1
public CarBuilder fixChassis();
// Этап 2
public CarBuilder fixBody();
// Этап 3
public CarBuilder paint();
// Этап 4
public CarBuilder fixInterior();
// Выпуск автомобиля
public Car build();
}
Обратите внимание, что я сделал тип CarBuilder типом возврата всех этапов, созданных здесь. Это позволит нам вызывать этапы по цепочке. Здесь есть один очень важный метод build, который заключается в том, чтобы получить результат или создать конечный объект Car. Этот метод фактически проверяет годность автомобиля и выпускает (возвращает) его только в том случае, если его сборка завершена успешно (все валидно).Шаг 3: Теперь пора написать ConcreteBuilder. Как я уже упоминал, у нас могут быть разные варианты ConcreteBuilder, и каждый из них выполняет сборку по-своему, чтобы предоставить нам различные представления сложного объекта Car.Итак, ниже приведен код ClassicCarBuilder, который собирает старые модели автомобилей.
package org.trishinfotech.builder;
public class ClassicCarBuilder implements CarBuilder {
private String chassis;
private String body;
private String paint;
private String interior;
public ClassicCarBuilder() {
super();
}
@Override
public CarBuilder fixChassis() {
System.out.println("Assembling chassis of the classical model");
this.chassis = "Classic Chassis";
return this;
}
@Override
public CarBuilder fixBody() {
System.out.println("Assembling body of the classical model");
this.body = "Classic Body";
return this;
}
@Override
public CarBuilder paint() {
System.out.println("Painting body of the classical model");
this.paint = "Classic White Paint";
return this;
}
@Override
public CarBuilder fixInterior() {
System.out.println("Setting up interior of the classical model");
this.interior = "Classic interior";
return this;
}
@Override
public Car build() {
Car car = new Car(chassis, body, paint, interior);
if (car.doQualityCheck()) {
return car;
} else {
System.out.println("Car assembly is incomplete. Can't deliver!");
}
return null;
}
}
Теперь напишем еще один строитель ModernCarBuilder для сборки последней модели автомобиля.
package org.trishinfotech.builder;
public class ModernCarBuilder implements CarBuilder {
private String chassis;
private String body;
private String paint;
private String interior;
public ModernCarBuilder() {
super();
}
@Override
public CarBuilder fixChassis() {
System.out.println("Assembling chassis of the modern model");
this.chassis = "Modern Chassis";
return this;
}
@Override
public CarBuilder fixBody() {
System.out.println("Assembling body of the modern model");
this.body = "Modern Body";
return this;
}
@Override
public CarBuilder paint() {
System.out.println("Painting body of the modern model");
this.paint = "Modern Black Paint";
return this;
}
@Override
public CarBuilder fixInterior() {
System.out.println("Setting up interior of the modern model");
this.interior = "Modern interior";
return this;
}
@Override
public Car build() {
Car car = new Car(chassis, body, paint, interior);
if (car.doQualityCheck()) {
return car;
} else {
System.out.println("Car assembly is incomplete. Can't deliver!");
}
return null;
}
}
И еще один SportsCarBuilder для создания спортивного автомобиля.
package org.trishinfotech.builder;
public class SportsCarBuilder implements CarBuilder {
private String chassis;
private String body;
private String paint;
private String interior;
public SportsCarBuilder() {
super();
}
@Override
public CarBuilder fixChassis() {
System.out.println("Assembling chassis of the sports model");
this.chassis = "Sporty Chassis";
return this;
}
@Override
public CarBuilder fixBody() {
System.out.println("Assembling body of the sports model");
this.body = "Sporty Body";
return this;
}
@Override
public CarBuilder paint() {
System.out.println("Painting body of the sports model");
this.paint = "Sporty Torch Red Paint";
return this;
}
@Override
public CarBuilder fixInterior() {
System.out.println("Setting up interior of the sports model");
this.interior = "Sporty interior";
return this;
}
@Override
public Car build() {
Car car = new Car(chassis, body, paint, interior);
if (car.doQualityCheck()) {
return car;
} else {
System.out.println("Car assembly is incomplete. Can't deliver!");
}
return null;
}
}
Шаг 4: Теперь мы напишем класс-распорядитель AutomotiveEngineer, под руководством которого строитель будет собирать автомобиль (объект Car) шаг за шагом в четко определенном порядке.
package org.trishinfotech.builder;
public class AutomotiveEngineer {
private CarBuilder builder;
public AutomotiveEngineer(CarBuilder builder) {
super();
this.builder = builder;
if (this.builder == null) {
throw new IllegalArgumentException("Automotive Engineer can't work without Car Builder!");
}
}
public Car manufactureCar() {
return builder.fixChassis().fixBody().paint().fixInterior().build();
}
}
Мы видим, что метод manufactureCar вызывает этапы сборки автомобиля в правильном порядке.Теперь пришло время написать класс Main для выполнения и тестирования нашего кода.
package org.trishinfotech.builder;
public class Main {
public static void main(String[] args) {
CarBuilder builder = new SportsCarBuilder();
AutomotiveEngineer engineer = new AutomotiveEngineer(builder);
Car car = engineer.manufactureCar();
if (car != null) {
System.out.println("Below car delievered: ");
System.out.println("======================================================================");
System.out.println(car);
System.out.println("======================================================================");
}
}
}
Ниже приведен вывод программы:
Assembling chassis of the sports model
Assembling body of the sports model
Painting body of the sports model
Setting up interior of the sports model
Below car delievered:
======================================================================
Car [chassis=Sporty Chassis, body=Sporty Body, paint=Sporty Torch Red Paint, interior=Sporty interior]
======================================================================
Я надеюсь, что вы хорошо разобрались в объяснении и примере, чтобы понять паттерн Builder. Некоторые из нас также находят у него сходство с паттерном абстрактной фабрики (Abstract Factory), о котором я рассказывал в другой статье. Основное различие между строителем и абстрактной фабрикой состоит в том, что строитель предоставляет нам больший или лучший контроль над процессом создания объекта. Если вкратце, то паттерн абстрактной фабрики отвечает на вопрос «что», а паттерн строитель - «как». Исходный код можно найти здесь: Real-Builder-Design-Pattern-Source-CodeЯ нашел паттерн Builder невероятно полезным и одним из наиболее часто используемых в приложениях в настоящее время. Я пришел к выводу, что Builder лучше подходит для работы с иммутабельными объектами. Все мы знаем, как много есть хороших иммутабельных объектов, и их использование увеличивается день ото дня, особенно после релиза Java 8.Я использую Builder для написания своих сложных иммутабельных классов, и я бы хотел продемонстриовать здесь эту идею.В качестве примера у нас есть класс Employee, в котором есть несколько полей.
public class Employee {
private int empNo;
private String name;
private String depttName;
private int salary;
private int mgrEmpNo;
private String projectName;
}
Предположим, только два поля EmpNo и EmpName являются обязательными, а все остальные - опциональные. Поскольку это иммутабельный класс, у меня есть два варианта написания конструкторов.
- Написать конструктор с параметрами под все поля.
- Написать несколько конструкторов для разных комбинаций параметров, чтобы создать разные представления объекта Employee.
Я решил, что первый вариант мне не подходит, так как мне не нравится, когда в методе больше трех-четырех параметров. Это выглядит не очень хорошо и становится еще хуже, когда многие параметры равны нулю или null.
Employee emp1 = new Employee (100, "Brijesh", null, 0, 0, "Builder Pattern");
Второй вариант тоже не очень хорош, так как мы создаем слишком много конструкторов.
public Employee(int empNo, String name) {
super();
if (empNo <= 0) {
throw new IllegalArgumentException("Please provide valid employee number.");
}
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("Please provide employee name.");
}
this.empNo = empNo;
this.name = name;
}
public Employee(int empNo, String name, String depttName) {
this(empNo, name);
this.depttName = depttName;
}
public Employee(int empNo, String name, String depttName, int salary) {
this(empNo, name, depttName);
this.salary = salary;
}
public Employee(int empNo, String name, String depttName, int salary, int mgrEmpNo) {
this(empNo, name, depttName, salary);
this.mgrEmpNo = mgrEmpNo;
}
public Employee(int empNo, String name, String depttName, int salary, int mgrEmpNo, String projectName) {
this(empNo, name, depttName, salary, mgrEmpNo);
this.projectName = projectName;
}
Итак, вот решение с помощью паттерна Builder:
package org.trishinfotech.builder.example;
public class Employee {
private int empNo;
private String name;
private String depttName;
private int salary;
private int mgrEmpNo;
private String projectName;
public Employee(EmployeeBuilder employeeBuilder) {
if (employeeBuilder == null) {
throw new IllegalArgumentException("Please provide employee builder to build employee object.");
}
if (employeeBuilder.empNo <= 0) {
throw new IllegalArgumentException("Please provide valid employee number.");
}
if (employeeBuilder.name == null || employeeBuilder.name.trim().isEmpty()) {
throw new IllegalArgumentException("Please provide employee name.");
}
this.empNo = employeeBuilder.empNo;
this.name = employeeBuilder.name;
this.depttName = employeeBuilder.depttName;
this.salary = employeeBuilder.salary;
this.mgrEmpNo = employeeBuilder.mgrEmpNo;
this.projectName = employeeBuilder.projectName;
}
public int getEmpNo() {
return empNo;
}
public String getName() {
return name;
}
public String getDepttName() {
return depttName;
}
public int getSalary() {
return salary;
}
public int getMgrEmpNo() {
return mgrEmpNo;
}
public String getProjectName() {
return projectName;
}
@Override
public String toString() {
// Класс StringBuilder также использует паттерн проектирования Builder с реализацией
// интерфейса java.lang.Appendable
StringBuilder builder = new StringBuilder();
builder.append("Employee [empNo=").append(empNo).append(", name=").append(name).append(", depttName=")
.append(depttName).append(", salary=").append(salary).append(", mgrEmpNo=").append(mgrEmpNo)
.append(", projectName=").append(projectName).append("]");
return builder.toString();
}
public static class EmployeeBuilder {
private int empNo;
protected String name;
protected String depttName;
protected int salary;
protected int mgrEmpNo;
protected String projectName;
public EmployeeBuilder() {
super();
}
public EmployeeBuilder empNo(int empNo) {
this.empNo = empNo;
return this;
}
public EmployeeBuilder name(String name) {
this.name = name;
return this;
}
public EmployeeBuilder depttName(String depttName) {
this.depttName = depttName;
return this;
}
public EmployeeBuilder salary(int salary) {
this.salary = salary;
return this;
}
public EmployeeBuilder mgrEmpNo(int mgrEmpNo) {
this.mgrEmpNo = mgrEmpNo;
return this;
}
public EmployeeBuilder projectName(String projectName) {
this.projectName = projectName;
return this;
}
public Employee build() {
Employee emp = null;
if (validateEmployee()) {
emp = new Employee(this);
} else {
System.out.println("Sorry! Employee objects can't be build without required details");
}
return emp;
}
private boolean validateEmployee() {
return (empNo > 0 && name != null && !name.trim().isEmpty());
}
}
}
Я написал EmployeeBuilder как публичный статический вложенный класс. Вы можете написать его как обычный публичный класс в отдельном файл Java. Большой разницы я не вижу.Теперь напишем программу EmployeeMain для создания объекта Employee:
package org.trishinfotech.builder.example;
public class EmployeeMain {
public static void main(String[] args) {
Employee emp1 = new Employee.EmployeeBuilder().empNo(100).name("Brijesh").projectName("Builder Pattern")
.build();
System.out.println(emp1);
}
}
Надеюсь, вам понравилась идея. Мы можем использовать это при создании более сложных объектов. Я не реализовал здесь распорядителя (Director), так как все шаги (сбор значений для полей) не являются обязательными и могут выполняться в любом порядке. Чтобы убедиться, что я создаю объект Employee только после получения всех обязательных полей, я написал метод проверки.Пример с оформлением заказа в ресторане с использованием паттерна BuilderЯ хочу еще показать вам пример кода для оформления заказа в ресторане, где Order (заказ) является иммутабельным объектом и требует тип обслуживания заказа - Order Service Type (Take Away - с собой/Eat Here - в заведении), всех необходимых нам продуктов питания (Food Items) и имени клиента (Customer Name - опционально) в время оформления заказа. Продуктов питания может быть сколько угодно. Итак, вот код этого примера.Код для перечисления OrderService:
package org.trishinfotech.builder;
public enum OrderService {
TAKE_AWAY("Take Away", 2.0d), EAT_HERE("Eat Here", 5.5d);
private String name;
private double tax;
OrderService(String name, double tax) {
this.name = name;
this.tax = tax;
}
public String getName() {
return name;
}
public double getTax() {
return tax;
}
}
Код для интерфейса FoodItem:
package org.trishinfotech.builder.meal;
import org.trishinfotech.builder.packing.Packing;
public interface FoodItem {
public String name();
public int calories();
public Packing packing();
public double price();
}
Код для класса Meal (блюдо). Класс Meal предлагает заранее определенные продукты питания со скидкой на цену товара (не на цену упаковки).
package org.trishinfotech.builder.meal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.trishinfotech.builder.packing.MultiPack;
import org.trishinfotech.builder.packing.Packing;
public class Meal implements FoodItem {
private List<FoodItem> foodItems = new ArrayList<FoodItem>();
private String mealName;
private double discount;
public Meal(String mealName, List<FoodItem> foodItems, double discount) {
super();
if (Objects.isNull(foodItems) || foodItems.stream().filter(Objects::nonNull).collect(Collectors.toList()).isEmpty()) {
throw new IllegalArgumentException(
"Meal can't be order without any food item");
}
this.mealName = mealName;
this.foodItems = new ArrayList<FoodItem>(foodItems);
this.discount = discount;
}
public List<FoodItem> getFoodItems() {
return foodItems;
}
@Override
public String name() {
return mealName;
}
@Override
public int calories() {
int totalCalories = foodItems.stream().mapToInt(foodItem -> foodItem.calories()).sum();
return totalCalories;
}
@Override
public Packing packing() {
double packingPrice = foodItems.stream().map(foodItem -> foodItem.packing())
.mapToDouble(packing -> packing.packingPrice()).sum();
return new MultiPack(packingPrice);
}
@Override
public double price() {
double totalPrice = foodItems.stream().mapToDouble(foodItem -> foodItem.price()).sum();
return totalPrice;
}
public double discount() {
return discount;
}
}
Еда:Код для класса Burger:
package org.trishinfotech.builder.food.burger;
import org.trishinfotech.builder.meal.FoodItem;
import org.trishinfotech.builder.packing.Packing;
import org.trishinfotech.builder.packing.Wrap;
public abstract class Burger implements FoodItem {
@Override
public Packing packing() {
return new Wrap();
}
}
Код для класса ChickenBurger:
package org.trishinfotech.builder.food.burger;
public class ChickenBurger extends Burger {
@Override
public String name() {
return "Chicken Burger";
}
@Override
public int calories() {
return 300;
}
@Override
public double price() {
return 4.5d;
}
}
Код для класса VegBurger (веганский бургер):
package org.trishinfotech.builder.food.burger;
public class VegBurger extends Burger {
@Override
public String name() {
return "Veg Burger";
}
@Override
public int calories() {
return 180;
}
@Override
public double price() {
return 2.7d;
}
}
Код для класса Nuggets:
package org.trishinfotech.builder.food.nuggets;
import org.trishinfotech.builder.meal.FoodItem;
import org.trishinfotech.builder.packing.Container;
import org.trishinfotech.builder.packing.Packing;
public abstract class Nuggets implements FoodItem {
@Override
public Packing packing() {
return new Container();
}
}
Код для класса CheeseNuggets:
package org.trishinfotech.builder.food.nuggets;
public class CheeseNuggets extends Nuggets {
@Override
public String name() {
return "Cheese Nuggets";
}
@Override
public int calories() {
return 330;
}
@Override
public double price() {
return 3.8d;
}
}
Код для класса ChickenNuggets:
package org.trishinfotech.builder.food.nuggets;
public class ChickenNuggets extends Nuggets {
@Override
public String name() {
return "Chicken Nuggets";
}
@Override
public int calories() {
return 450;
}
@Override
public double price() {
return 5.0d;
}
}
Напитки:Напитки бывают разных размеров. Итак, вот код перечисления BeverageSize:
package org.trishinfotech.builder.beverages;
public enum BeverageSize {
XS("Extra Small", 110), S("Small", 150), M("Medium", 210), L("Large", 290);
private String name;
private int calories;
BeverageSize(String name, int calories) {
this.name = name;
this.calories = calories;
}
public String getName() {
return name;
}
public int getCalories() {
return calories;
}
}
Код для класса Drink:
package org.trishinfotech.builder.beverages;
import org.trishinfotech.builder.meal.FoodItem;
public abstract class Drink implements FoodItem {
protected BeverageSize size;
public Drink(BeverageSize size) {
super();
this.size = size;
if (this.size == null) {
this.size = BeverageSize.M;
}
}
public BeverageSize getSize() {
return size;
}
public String drinkDetails() {
return " (" + size + ")";
}
}
Код для класса ColdDrink:
package org.trishinfotech.builder.beverages.cold;
import org.trishinfotech.builder.beverages.BeverageSize;
import org.trishinfotech.builder.beverages.Drink;
import org.trishinfotech.builder.packing.Bottle;
import org.trishinfotech.builder.packing.Packing;
public abstract class ColdDrink extends Drink {
public ColdDrink(BeverageSize size) {
super(size);
}
@Override public Packing packing() {
return new Bottle();
}
}
Код для класса CocaCola:
package org.trishinfotech.builder.beverages.cold;
import org.trishinfotech.builder.beverages.BeverageSize;
public class CocaCola extends ColdDrink {
public CocaCola(BeverageSize size) {
super(size);
}
@Override
public String name() {
return "Coca-Cola" + drinkDetails();
}
@Override
public int calories() {
if (size != null) {
switch (size) {
case XS:
return 110;
case S:
return 150;
case M:
return 210;
case L:
return 290;
default:
break;
}
}
return 0;
}
@Override
public double price() {
if (size != null) {
switch (size) {
case XS:
return 0.80d;
case S:
return 1.0d;
case M:
return 1.5d;
case L:
return 2.0d;
default:
break;
}
}
return 0.0d;
}
}
Код для класса Pepsi:
package org.trishinfotech.builder.beverages.cold;
import org.trishinfotech.builder.beverages.BeverageSize;
public class Pepsi extends ColdDrink {
public Pepsi(BeverageSize size) {
super(size);
}
@Override public String name() {
return "Pepsi" + drinkDetails();
}
@Override public int calories() {
if (size != null) {
switch (size) {
case S:
return 160;
case M:
return 220;
case L:
return 300;
default:
break;
}
}
return 0;
}
@Override public double price() {
if (size != null) {
switch (size) {
case S:
return 1.2d;
case M:
return 2.2d;
case L:
return 2.7d;
default:
break;
}
}
return 0.0d;
}
}
Код для класса HotDrink:
package org.trishinfotech.builder.beverages.hot;
import org.trishinfotech.builder.beverages.BeverageSize;
import org.trishinfotech.builder.beverages.Drink;
import org.trishinfotech.builder.packing.Packing;
import org.trishinfotech.builder.packing.SipperMug;
public abstract class HotDrink extends Drink {
public HotDrink(BeverageSize size) {
super(size);
}
@Override public Packing packing() {
return new SipperMug();
}
}
Код для класса Cuppuccinno:
package org.trishinfotech.builder.beverages.hot;
import org.trishinfotech.builder.beverages.BeverageSize;
public class Cappuccino extends HotDrink {
public Cappuccino(BeverageSize size) {
super(size);
}
@Override public String name() {
return "Cappuccino" + drinkDetails();
}
@Override public int calories() {
if (size != null) {
switch (size) {
case S:
return 120;
case M:
return 160;
case L:
return 210;
default:
break;
}
}
return 0;
}
@Override public double price() {
if (size != null) {
switch (size) {
case S:
return 1.0d;
case M:
return 1.4d;
case L:
return 1.8d;
default:
break;
}
}
return 0.0d;
}
}
Код для класса HotChocolate:
package org.trishinfotech.builder.beverages.hot;
import org.trishinfotech.builder.beverages.BeverageSize;
public class HotChocolate extends HotDrink {
public HotChocolate(BeverageSize size) {
super(size);
}
@Override public String name() {
return "Hot Chocolate" + drinkDetails();
}
@Override public int calories() {
if (size != null) {
switch (size) {
case S:
return 370;
case M:
return 450;
case L:
return 560;
default:
break;
}
}
return 0;
}
@Override public double price() {
if (size != null) {
switch (size) {
case S:
return 1.6d;
case M:
return 2.3d;
case L:
return 3.0d;
default:
break;
}
}
return 0.0d;
}
}
Упаковка:Код интерфейса Packing:
package org.trishinfotech.builder.packing;
public interface Packing {
public String pack();
public double packingPrice();
}
Код для класса Bottle:
package org.trishinfotech.builder.packing;
public class Bottle implements Packing {
@Override
public String pack() {
return "Bottle";
}
@Override
public double packingPrice() {
return 0.75d;
}
}
Код для класса Container:
package org.trishinfotech.builder.packing;
public class Container implements Packing {
@Override
public String pack() {
return "Container";
}
@Override
public double packingPrice() {
return 1.25d;
}
}
Код для класса MultiPack. Упаковка MutiPack служит вспомогательной упаковкой для еды, когда мы используем разные упаковки для разных продуктов.
package org.trishinfotech.builder.packing;
public class MultiPack implements Packing {
private double packingPrice;
public MultiPack(double packingPrice) {
super();
this.packingPrice = packingPrice;
}
@Override
public String pack() {
return "Multi-Pack";
}
@Override
public double packingPrice() {
return packingPrice;
}
}
Код для класса SipperMug:
package org.trishinfotech.builder.packing;
public class SipperMug implements Packing {
@Override
public String pack() {
return "Sipper Mug";
}
@Override
public double packingPrice() {
return 1.6d;
}
}
Код для класса Wrap:
package org.trishinfotech.builder.packing;
public class Wrap implements Packing {
@Override
public String pack() {
return "Wrap";
}
@Override
public double packingPrice() {
return 0.40d;
}
}
Код служебного класса BillPrinter, который я написал для печати детализированного счета.
package org.trishinfotech.builder.util;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.atomic.DoubleAdder;
import org.trishinfotech.builder.Order;
import org.trishinfotech.builder.OrderService;
import org.trishinfotech.builder.meal.Meal;
import org.trishinfotech.builder.packing.Packing;
public class BillPrinter {
static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
public static void printItemisedBill(Order order) {
OrderService service = order.getService();
System.out.printf("%60s\n", "Food Court");
System.out.println("=================================================================================================================");
System.out.printf("Service: %10s (%2.2f Tax) Customer Name: %-20s\n", service.getName(), service.getTax(), order.getCustomerName());
System.out.println("-----------------------------------------------------------------------------------------------------------------");
System.out.printf("%25s | %10s | %10s | %10s | %15s | %10s | %10s\n", "Food Item", "Calories", "Packing", "Price", "Packing Price", "Discount %", "Total Price");
System.out.println("-----------------------------------------------------------------------------------------------------------------");
DoubleAdder itemTotalPrice = new DoubleAdder();
order.getFoodItems().stream().forEach(item -> {
String name = item.name();
int calories = item.calories();
Packing packing = item.packing();
double price = item.price();
double packingPrice = packing.packingPrice();
double discount = item instanceof Meal? ((Meal)item).discount() : 0.0d;
double totalItemPrice = calculateTotalItemPrice(price, packingPrice, discount);
System.out.printf("%25s | %10d | %10s | %10.2f | %15.2f | %10.2f | %10.2f\n", name, calories, packing.pack(), price, packing.packingPrice(), discount, totalItemPrice);
itemTotalPrice.add(totalItemPrice);
});
System.out.println("=================================================================================================================");
double billTotal = itemTotalPrice.doubleValue();
billTotal = applyTaxes(billTotal, service);
System.out.printf("Date: %-30s %66s %.2f\n", dtf.format(LocalDateTime.now()), "Total Bill (incl. taxes):", billTotal);
System.out.println("Enjoy your meal!\n\n\n\n");
}
private static double applyTaxes(double billTotal, OrderService service) {
return billTotal + (billTotal * service.getTax())/100;
}
private static double calculateTotalItemPrice(double price, double packingPrice, double discount) {
if (discount > 0.0d) {
price = price - (price * discount)/100;
}
return price + packingPrice;
}
}
Почти все готово. Пришло время написать наш иммутабельный класс Order:
package org.trishinfotech.builder;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.trishinfotech.builder.meal.FoodItem;
public class Order {
private List<FoodItem> foodItems = new ArrayList<FoodItem>();
private String customerName;
private OrderService service;
public Order(OrderService service, List<FoodItem> foodItems, String customerName) {
super();
if (Objects.isNull(service)) {
throw new IllegalArgumentException(
"Meal can't be order without selecting service 'Take Away' or 'Eat Here'");
}
if (Objects.isNull(foodItems) || foodItems.stream().filter(Objects::nonNull).collect(Collectors.toList()).isEmpty()) {
throw new IllegalArgumentException(
"Meal can't be order without any food item");
}
this.service = service;
this.foodItems = new ArrayList<FoodItem>(foodItems);
this.customerName = customerName;
if (this.customerName == null) {
this.customerName = "NO NAME";
}
}
public List<FoodItem> getFoodItems() {
return foodItems;
}
public String getCustomerName() {
return customerName;
}
public OrderService getService() {
return service;
}
}
А вот код для OrderBuilder, который конструирует объект Order.
package org.trishinfotech.builder;
import java.util.ArrayList;
import java.util.List;
import org.trishinfotech.builder.beverages.BeverageSize;
import org.trishinfotech.builder.beverages.cold.CocaCola;
import org.trishinfotech.builder.beverages.cold.Pepsi;
import org.trishinfotech.builder.food.burger.ChickenBurger;
import org.trishinfotech.builder.food.burger.VegBurger;
import org.trishinfotech.builder.food.nuggets.CheeseNuggets;
import org.trishinfotech.builder.food.nuggets.ChickenNuggets;
import org.trishinfotech.builder.meal.FoodItem;
import org.trishinfotech.builder.meal.Meal;
public class OrderBuilder {
protected static final double HAPPY_MENU_DISCOUNT = 5.0d;
private String customerName;
private OrderService service = OrderService.TAKE_AWAY;
private List<FoodItem> items = new ArrayList<FoodItem>();
public OrderBuilder() {
super();
}
// Сеттеры для каждого поля в целевом объекте. В этом примере это Order.
// Возвращаемым типом у нас будет сам Builder (например, OrderBuilder), чтобы сделать возможным цепной вызов сеттеров.
public OrderBuilder name(String customerName) {
this.customerName = customerName;
return this;
}
public OrderBuilder service(OrderService service) {
if (service != null) {
this.service = service;
}
return this;
}
public OrderBuilder item(FoodItem item) {
items.add(item);
return this;
}
// Комбо предложения
public OrderBuilder vegNuggetsHappyMeal() {
List<FoodItem> foodItems = new ArrayList<FoodItem>();
foodItems.add(new CheeseNuggets());
foodItems.add(new Pepsi(BeverageSize.S));
Meal meal = new Meal("Veg Nuggets Happy Meal", foodItems, HAPPY_MENU_DISCOUNT);
return item(meal);
}
public OrderBuilder chickenNuggetsHappyMeal() {
List<FoodItem> foodItems = new ArrayList<FoodItem>();
foodItems.add(new ChickenNuggets());
foodItems.add(new CocaCola(BeverageSize.S));
Meal meal = new Meal("Chicken Nuggets Happy Meal", foodItems, HAPPY_MENU_DISCOUNT);
return item(meal);
}
public OrderBuilder vegBurgerHappyMeal() {
List<FoodItem> foodItems = new ArrayList<FoodItem>();
foodItems.add(new VegBurger());
foodItems.add(new Pepsi(BeverageSize.S));
Meal meal = new Meal("Veg Burger Happy Meal", foodItems, HAPPY_MENU_DISCOUNT);
return item(meal);
}
public OrderBuilder chickenBurgerHappyMeal() {
List<FoodItem> foodItems = new ArrayList<FoodItem>();
foodItems.add(new ChickenBurger());
foodItems.add(new CocaCola(BeverageSize.S));
Meal meal = new Meal("Chicken Burger Happy Meal", foodItems, HAPPY_MENU_DISCOUNT);
return item(meal);
}
public Order build() {
Order order = new Order(service, items, customerName);
if (!validateOrder()) {
System.out.println("Sorry! Order can't be placed without service type (Take Away/Eat Here) and any food item.");
return null;
}
return order;
}
private boolean validateOrder() {
return (service != null) && !items.isEmpty();
}
}
Готово! Теперь пришло время написать Main для выполнения и тестирования результат:
package org.trishinfotech.builder;
import org.trishinfotech.builder.beverages.BeverageSize;
import org.trishinfotech.builder.beverages.cold.CocaCola;
import org.trishinfotech.builder.beverages.cold.Pepsi;
import org.trishinfotech.builder.beverages.hot.HotChocolate;
import org.trishinfotech.builder.food.burger.ChickenBurger;
import org.trishinfotech.builder.food.nuggets.CheeseNuggets;
import org.trishinfotech.builder.food.nuggets.ChickenNuggets;
import org.trishinfotech.builder.util.BillPrinter;
public class Main {
public static void main(String[] args) {
OrderBuilder builder1 = new OrderBuilder();
// you can see the use of chained calls of setters here. No statement terminator
// till we set all the values of the object
Order meal1 = builder1.name("Brijesh").service(OrderService.TAKE_AWAY).item(new ChickenBurger())
.item(new Pepsi(BeverageSize.M)).vegNuggetsHappyMeal().build();
BillPrinter.printItemisedBill(meal1);
OrderBuilder builder2 = new OrderBuilder();
Order meal2 = builder2.name("Micheal").service(OrderService.EAT_HERE).item(new ChickenNuggets())
.item(new CheeseNuggets()).item(new CocaCola(BeverageSize.L)).chickenBurgerHappyMeal()
.item(new HotChocolate(BeverageSize.M)).vegBurgerHappyMeal().build();
BillPrinter.printItemisedBill(meal2);
}
}
А вот и результат работы программы:
Food Court
=================================================================================================================
Service: Take Away (2.00 Tax) Customer Name: Brijesh
-----------------------------------------------------------------------------------------------------------------
Food Item | Calories | Packing | Price | Packing Price | Discount % | Total Price
-----------------------------------------------------------------------------------------------------------------
Chicken Burger | 300 | Wrap | 4.50 | 0.40 | 0.00 | 4.90
Pepsi (M) | 220 | Bottle | 2.20 | 0.75 | 0.00 | 2.95
Veg Nuggets Happy Meal | 490 | Multi-Pack | 5.00 | 2.00 | 5.00 | 6.75
=================================================================================================================
Date: 2020/10/09 20:02:38 Total Bill (incl. taxes): 14.89
Enjoy your meal!
Food Court
=================================================================================================================
Service: Eat Here (5.50 Tax) Customer Name: Micheal
-----------------------------------------------------------------------------------------------------------------
Food Item | Calories | Packing | Price | Packing Price | Discount % | Total Price
-----------------------------------------------------------------------------------------------------------------
Chicken Nuggets | 450 | Container | 5.00 | 1.25 | 0.00 | 6.25
Cheese Nuggets | 330 | Container | 3.80 | 1.25 | 0.00 | 5.05
Coca-Cola (L) | 290 | Bottle | 2.00 | 0.75 | 0.00 | 2.75
Chicken Burger Happy Meal | 450 | Multi-Pack | 5.50 | 1.15 | 5.00 | 6.38
Hot Chocolate (M) | 450 | Sipper Mug | 2.30 | 1.60 | 0.00 | 3.90
Veg Burger Happy Meal | 340 | Multi-Pack | 3.90 | 1.15 | 5.00 | 4.86
=================================================================================================================
Date: 2020/10/09 20:02:38 Total Bill (incl. taxes): 30.78
Enjoy your meal!
Ну вот и все! Я надеюсь, что этот урок помог освоить паттерн Builder.Исходный код можно найти здесь: Real-Builder-Design-Pattern-Source-Codeи здесь: Builder-Design-Pattern-Sample-Code
Узнать подробнее о курсе «Архитектура и шаблоны проектирования». Смотреть вебинар «Шаблоны GRASP».
===========
Источник:
habr.com
===========
===========
Автор оригинала: Brijesh Saxena
===========Похожие новости:
- [Разработка веб-сайтов, JavaScript, VueJS] Микрофронтенды: разделяй и властвуй
- [Java] Подключение Keycloak к Spring Boot приложению
- [Python, Программирование, Интерфейсы, DIY или Сделай сам] Что не так с вашей консольной программой?
- [JavaScript, Программирование, ReactJS, Учебный процесс в IT] React: наглядное пособие для начинающих. Создаем свой компонент без знаний JavaScript (перевод)
- [Python, JavaScript, Программирование, Учебный процесс в IT] Ontol: подборка видео-лекций и каналов для продвинутых программистов
- [Программирование, Prolog] Логическое программирование на Prolog для чайников
- [JavaScript, Медийная реклама] «Продам гараж»: фронт и реклама в hh.ru
- [Программирование, jQuery] Сборники рецептов jq
- [Разработка веб-сайтов, PHP, Программирование, Go] Strategy Design Pattern
- [Программирование, Разработка под iOS, Objective C, Swift] Связанные не явные выражения в Swift 5.4 (перевод)
Теги для поиска: #_programmirovanie (Программирование), #_java, #_proektirovanie_i_refaktoring (Проектирование и рефакторинг), #_java, #_grasp, #_arhitektura_po (архитектура по), #_patterny_proektirovanija (паттерны проектирования), #_blog_kompanii_otus (
Блог компании OTUS
), #_programmirovanie (
Программирование
), #_java, #_proektirovanie_i_refaktoring (
Проектирование и рефакторинг
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 27-Апр 05:21
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 7 лет 2 месяца |
|
В преддверии скорого старта курса «Архитектура и шаблоны проектирования» делимся с вами переводом материала.
Приглашаем также всех желающих на открытый демо-урок «Шаблоны GRASP». На этом занятии мы проанализируем функциональное разделение функционала и рассмотрим 9 шаблонов GRASP. Присоединяйтесь! ![]() А вот и я со своей очередной статьей о паттернах проектирования, а именно о паттерне проектирования Builder (он же Строитель). Очень полезный паттерн проектирования, который позволяет нам шаг за шагом конструировать сложные объекты.Паттерн проектирования Builder
![]() Итак, паттерн проектирования Builder можно разбить на следующие важные компоненты:
package org.trishinfotech.builder;
public class Car { private String chassis; private String body; private String paint; private String interior; public Car() { super(); } public Car(String chassis, String body, String paint, String interior) { this(); this.chassis = chassis; this.body = body; this.paint = paint; this.interior = interior; } public String getChassis() { return chassis; } public void setChassis(String chassis) { this.chassis = chassis; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public String getPaint() { return paint; } public void setPaint(String paint) { this.paint = paint; } public String getInterior() { return interior; } public void setInterior(String interior) { this.interior = interior; } public boolean doQualityCheck() { return (chassis != null && !chassis.trim().isEmpty()) && (body != null && !body.trim().isEmpty()) && (paint != null && !paint.trim().isEmpty()) && (interior != null && !interior.trim().isEmpty()); } @Override public String toString() { // StringBuilder class also uses Builder Design Pattern with implementation of java.lang.Appendable interface StringBuilder builder = new StringBuilder(); builder.append("Car [chassis=").append(chassis).append(", body=").append(body).append(", paint=").append(paint) return builder.toString(); } } package org.trishinfotech.builder;
public interface CarBuilder { // Этап 1 public CarBuilder fixChassis(); // Этап 2 public CarBuilder fixBody(); // Этап 3 public CarBuilder paint(); // Этап 4 public CarBuilder fixInterior(); // Выпуск автомобиля public Car build(); } package org.trishinfotech.builder;
public class ClassicCarBuilder implements CarBuilder { private String chassis; private String body; private String paint; private String interior; public ClassicCarBuilder() { super(); } @Override public CarBuilder fixChassis() { System.out.println("Assembling chassis of the classical model"); this.chassis = "Classic Chassis"; return this; } @Override public CarBuilder fixBody() { System.out.println("Assembling body of the classical model"); this.body = "Classic Body"; return this; } @Override public CarBuilder paint() { System.out.println("Painting body of the classical model"); this.paint = "Classic White Paint"; return this; } @Override public CarBuilder fixInterior() { System.out.println("Setting up interior of the classical model"); this.interior = "Classic interior"; return this; } @Override public Car build() { Car car = new Car(chassis, body, paint, interior); if (car.doQualityCheck()) { return car; } else { System.out.println("Car assembly is incomplete. Can't deliver!"); } return null; } } package org.trishinfotech.builder;
public class ModernCarBuilder implements CarBuilder { private String chassis; private String body; private String paint; private String interior; public ModernCarBuilder() { super(); } @Override public CarBuilder fixChassis() { System.out.println("Assembling chassis of the modern model"); this.chassis = "Modern Chassis"; return this; } @Override public CarBuilder fixBody() { System.out.println("Assembling body of the modern model"); this.body = "Modern Body"; return this; } @Override public CarBuilder paint() { System.out.println("Painting body of the modern model"); this.paint = "Modern Black Paint"; return this; } @Override public CarBuilder fixInterior() { System.out.println("Setting up interior of the modern model"); this.interior = "Modern interior"; return this; } @Override public Car build() { Car car = new Car(chassis, body, paint, interior); if (car.doQualityCheck()) { return car; } else { System.out.println("Car assembly is incomplete. Can't deliver!"); } return null; } } package org.trishinfotech.builder;
public class SportsCarBuilder implements CarBuilder { private String chassis; private String body; private String paint; private String interior; public SportsCarBuilder() { super(); } @Override public CarBuilder fixChassis() { System.out.println("Assembling chassis of the sports model"); this.chassis = "Sporty Chassis"; return this; } @Override public CarBuilder fixBody() { System.out.println("Assembling body of the sports model"); this.body = "Sporty Body"; return this; } @Override public CarBuilder paint() { System.out.println("Painting body of the sports model"); this.paint = "Sporty Torch Red Paint"; return this; } @Override public CarBuilder fixInterior() { System.out.println("Setting up interior of the sports model"); this.interior = "Sporty interior"; return this; } @Override public Car build() { Car car = new Car(chassis, body, paint, interior); if (car.doQualityCheck()) { return car; } else { System.out.println("Car assembly is incomplete. Can't deliver!"); } return null; } } package org.trishinfotech.builder;
public class AutomotiveEngineer { private CarBuilder builder; public AutomotiveEngineer(CarBuilder builder) { super(); this.builder = builder; if (this.builder == null) { throw new IllegalArgumentException("Automotive Engineer can't work without Car Builder!"); } } public Car manufactureCar() { return builder.fixChassis().fixBody().paint().fixInterior().build(); } } package org.trishinfotech.builder;
public class Main { public static void main(String[] args) { CarBuilder builder = new SportsCarBuilder(); AutomotiveEngineer engineer = new AutomotiveEngineer(builder); Car car = engineer.manufactureCar(); if (car != null) { System.out.println("Below car delievered: "); System.out.println("======================================================================"); System.out.println(car); System.out.println("======================================================================"); } } } Assembling chassis of the sports model
Assembling body of the sports model Painting body of the sports model Setting up interior of the sports model Below car delievered: ====================================================================== Car [chassis=Sporty Chassis, body=Sporty Body, paint=Sporty Torch Red Paint, interior=Sporty interior] ====================================================================== public class Employee {
private int empNo; private String name; private String depttName; private int salary; private int mgrEmpNo; private String projectName; }
Employee emp1 = new Employee (100, "Brijesh", null, 0, 0, "Builder Pattern");
public Employee(int empNo, String name) {
super(); if (empNo <= 0) { throw new IllegalArgumentException("Please provide valid employee number."); } if (name == null || name.trim().isEmpty()) { throw new IllegalArgumentException("Please provide employee name."); } this.empNo = empNo; this.name = name; } public Employee(int empNo, String name, String depttName) { this(empNo, name); this.depttName = depttName; } public Employee(int empNo, String name, String depttName, int salary) { this(empNo, name, depttName); this.salary = salary; } public Employee(int empNo, String name, String depttName, int salary, int mgrEmpNo) { this(empNo, name, depttName, salary); this.mgrEmpNo = mgrEmpNo; } public Employee(int empNo, String name, String depttName, int salary, int mgrEmpNo, String projectName) { this(empNo, name, depttName, salary, mgrEmpNo); this.projectName = projectName; } package org.trishinfotech.builder.example;
public class Employee { private int empNo; private String name; private String depttName; private int salary; private int mgrEmpNo; private String projectName; public Employee(EmployeeBuilder employeeBuilder) { if (employeeBuilder == null) { throw new IllegalArgumentException("Please provide employee builder to build employee object."); } if (employeeBuilder.empNo <= 0) { throw new IllegalArgumentException("Please provide valid employee number."); } if (employeeBuilder.name == null || employeeBuilder.name.trim().isEmpty()) { throw new IllegalArgumentException("Please provide employee name."); } this.empNo = employeeBuilder.empNo; this.name = employeeBuilder.name; this.depttName = employeeBuilder.depttName; this.salary = employeeBuilder.salary; this.mgrEmpNo = employeeBuilder.mgrEmpNo; this.projectName = employeeBuilder.projectName; } public int getEmpNo() { return empNo; } public String getName() { return name; } public String getDepttName() { return depttName; } public int getSalary() { return salary; } public int getMgrEmpNo() { return mgrEmpNo; } public String getProjectName() { return projectName; } @Override public String toString() { // Класс StringBuilder также использует паттерн проектирования Builder с реализацией // интерфейса java.lang.Appendable StringBuilder builder = new StringBuilder(); builder.append("Employee [empNo=").append(empNo).append(", name=").append(name).append(", depttName=") .append(depttName).append(", salary=").append(salary).append(", mgrEmpNo=").append(mgrEmpNo) .append(", projectName=").append(projectName).append("]"); return builder.toString(); } public static class EmployeeBuilder { private int empNo; protected String name; protected String depttName; protected int salary; protected int mgrEmpNo; protected String projectName; public EmployeeBuilder() { super(); } public EmployeeBuilder empNo(int empNo) { this.empNo = empNo; return this; } public EmployeeBuilder name(String name) { this.name = name; return this; } public EmployeeBuilder depttName(String depttName) { this.depttName = depttName; return this; } public EmployeeBuilder salary(int salary) { this.salary = salary; return this; } public EmployeeBuilder mgrEmpNo(int mgrEmpNo) { this.mgrEmpNo = mgrEmpNo; return this; } public EmployeeBuilder projectName(String projectName) { this.projectName = projectName; return this; } public Employee build() { Employee emp = null; if (validateEmployee()) { emp = new Employee(this); } else { System.out.println("Sorry! Employee objects can't be build without required details"); } return emp; } private boolean validateEmployee() { return (empNo > 0 && name != null && !name.trim().isEmpty()); } } } package org.trishinfotech.builder.example;
public class EmployeeMain { public static void main(String[] args) { Employee emp1 = new Employee.EmployeeBuilder().empNo(100).name("Brijesh").projectName("Builder Pattern") .build(); System.out.println(emp1); } } package org.trishinfotech.builder;
public enum OrderService { TAKE_AWAY("Take Away", 2.0d), EAT_HERE("Eat Here", 5.5d); private String name; private double tax; OrderService(String name, double tax) { this.name = name; this.tax = tax; } public String getName() { return name; } public double getTax() { return tax; } } package org.trishinfotech.builder.meal;
import org.trishinfotech.builder.packing.Packing; public interface FoodItem { public String name(); public int calories(); public Packing packing(); public double price(); } package org.trishinfotech.builder.meal;
import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import org.trishinfotech.builder.packing.MultiPack; import org.trishinfotech.builder.packing.Packing; public class Meal implements FoodItem { private List<FoodItem> foodItems = new ArrayList<FoodItem>(); private String mealName; private double discount; public Meal(String mealName, List<FoodItem> foodItems, double discount) { super(); if (Objects.isNull(foodItems) || foodItems.stream().filter(Objects::nonNull).collect(Collectors.toList()).isEmpty()) { throw new IllegalArgumentException( "Meal can't be order without any food item"); } this.mealName = mealName; this.foodItems = new ArrayList<FoodItem>(foodItems); this.discount = discount; } public List<FoodItem> getFoodItems() { return foodItems; } @Override public String name() { return mealName; } @Override public int calories() { int totalCalories = foodItems.stream().mapToInt(foodItem -> foodItem.calories()).sum(); return totalCalories; } @Override public Packing packing() { double packingPrice = foodItems.stream().map(foodItem -> foodItem.packing()) .mapToDouble(packing -> packing.packingPrice()).sum(); return new MultiPack(packingPrice); } @Override public double price() { double totalPrice = foodItems.stream().mapToDouble(foodItem -> foodItem.price()).sum(); return totalPrice; } public double discount() { return discount; } } package org.trishinfotech.builder.food.burger;
import org.trishinfotech.builder.meal.FoodItem; import org.trishinfotech.builder.packing.Packing; import org.trishinfotech.builder.packing.Wrap; public abstract class Burger implements FoodItem { @Override public Packing packing() { return new Wrap(); } } package org.trishinfotech.builder.food.burger;
public class ChickenBurger extends Burger { @Override public String name() { return "Chicken Burger"; } @Override public int calories() { return 300; } @Override public double price() { return 4.5d; } } package org.trishinfotech.builder.food.burger;
public class VegBurger extends Burger { @Override public String name() { return "Veg Burger"; } @Override public int calories() { return 180; } @Override public double price() { return 2.7d; } } package org.trishinfotech.builder.food.nuggets;
import org.trishinfotech.builder.meal.FoodItem; import org.trishinfotech.builder.packing.Container; import org.trishinfotech.builder.packing.Packing; public abstract class Nuggets implements FoodItem { @Override public Packing packing() { return new Container(); } } package org.trishinfotech.builder.food.nuggets;
public class CheeseNuggets extends Nuggets { @Override public String name() { return "Cheese Nuggets"; } @Override public int calories() { return 330; } @Override public double price() { return 3.8d; } } package org.trishinfotech.builder.food.nuggets;
public class ChickenNuggets extends Nuggets { @Override public String name() { return "Chicken Nuggets"; } @Override public int calories() { return 450; } @Override public double price() { return 5.0d; } } package org.trishinfotech.builder.beverages;
public enum BeverageSize { XS("Extra Small", 110), S("Small", 150), M("Medium", 210), L("Large", 290); private String name; private int calories; BeverageSize(String name, int calories) { this.name = name; this.calories = calories; } public String getName() { return name; } public int getCalories() { return calories; } } package org.trishinfotech.builder.beverages;
import org.trishinfotech.builder.meal.FoodItem; public abstract class Drink implements FoodItem { protected BeverageSize size; public Drink(BeverageSize size) { super(); this.size = size; if (this.size == null) { this.size = BeverageSize.M; } } public BeverageSize getSize() { return size; } public String drinkDetails() { return " (" + size + ")"; } } package org.trishinfotech.builder.beverages.cold;
import org.trishinfotech.builder.beverages.BeverageSize; import org.trishinfotech.builder.beverages.Drink; import org.trishinfotech.builder.packing.Bottle; import org.trishinfotech.builder.packing.Packing; public abstract class ColdDrink extends Drink { public ColdDrink(BeverageSize size) { super(size); } @Override public Packing packing() { return new Bottle(); } } package org.trishinfotech.builder.beverages.cold;
import org.trishinfotech.builder.beverages.BeverageSize; public class CocaCola extends ColdDrink { public CocaCola(BeverageSize size) { super(size); } @Override public String name() { return "Coca-Cola" + drinkDetails(); } @Override public int calories() { if (size != null) { switch (size) { case XS: return 110; case S: return 150; case M: return 210; case L: return 290; default: break; } } return 0; } @Override public double price() { if (size != null) { switch (size) { case XS: return 0.80d; case S: return 1.0d; case M: return 1.5d; case L: return 2.0d; default: break; } } return 0.0d; } } package org.trishinfotech.builder.beverages.cold;
import org.trishinfotech.builder.beverages.BeverageSize; public class Pepsi extends ColdDrink { public Pepsi(BeverageSize size) { super(size); } @Override public String name() { return "Pepsi" + drinkDetails(); } @Override public int calories() { if (size != null) { switch (size) { case S: return 160; case M: return 220; case L: return 300; default: break; } } return 0; } @Override public double price() { if (size != null) { switch (size) { case S: return 1.2d; case M: return 2.2d; case L: return 2.7d; default: break; } } return 0.0d; } } package org.trishinfotech.builder.beverages.hot;
import org.trishinfotech.builder.beverages.BeverageSize; import org.trishinfotech.builder.beverages.Drink; import org.trishinfotech.builder.packing.Packing; import org.trishinfotech.builder.packing.SipperMug; public abstract class HotDrink extends Drink { public HotDrink(BeverageSize size) { super(size); } @Override public Packing packing() { return new SipperMug(); } } package org.trishinfotech.builder.beverages.hot;
import org.trishinfotech.builder.beverages.BeverageSize; public class Cappuccino extends HotDrink { public Cappuccino(BeverageSize size) { super(size); } @Override public String name() { return "Cappuccino" + drinkDetails(); } @Override public int calories() { if (size != null) { switch (size) { case S: return 120; case M: return 160; case L: return 210; default: break; } } return 0; } @Override public double price() { if (size != null) { switch (size) { case S: return 1.0d; case M: return 1.4d; case L: return 1.8d; default: break; } } return 0.0d; } } package org.trishinfotech.builder.beverages.hot;
import org.trishinfotech.builder.beverages.BeverageSize; public class HotChocolate extends HotDrink { public HotChocolate(BeverageSize size) { super(size); } @Override public String name() { return "Hot Chocolate" + drinkDetails(); } @Override public int calories() { if (size != null) { switch (size) { case S: return 370; case M: return 450; case L: return 560; default: break; } } return 0; } @Override public double price() { if (size != null) { switch (size) { case S: return 1.6d; case M: return 2.3d; case L: return 3.0d; default: break; } } return 0.0d; } } package org.trishinfotech.builder.packing;
public interface Packing { public String pack(); public double packingPrice(); } package org.trishinfotech.builder.packing;
public class Bottle implements Packing { @Override public String pack() { return "Bottle"; } @Override public double packingPrice() { return 0.75d; } } package org.trishinfotech.builder.packing;
public class Container implements Packing { @Override public String pack() { return "Container"; } @Override public double packingPrice() { return 1.25d; } } package org.trishinfotech.builder.packing;
public class MultiPack implements Packing { private double packingPrice; public MultiPack(double packingPrice) { super(); this.packingPrice = packingPrice; } @Override public String pack() { return "Multi-Pack"; } @Override public double packingPrice() { return packingPrice; } } package org.trishinfotech.builder.packing;
public class SipperMug implements Packing { @Override public String pack() { return "Sipper Mug"; } @Override public double packingPrice() { return 1.6d; } } package org.trishinfotech.builder.packing;
public class Wrap implements Packing { @Override public String pack() { return "Wrap"; } @Override public double packingPrice() { return 0.40d; } } package org.trishinfotech.builder.util;
import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.concurrent.atomic.DoubleAdder; import org.trishinfotech.builder.Order; import org.trishinfotech.builder.OrderService; import org.trishinfotech.builder.meal.Meal; import org.trishinfotech.builder.packing.Packing; public class BillPrinter { static DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"); public static void printItemisedBill(Order order) { OrderService service = order.getService(); System.out.printf("%60s\n", "Food Court"); System.out.println("================================================================================================================="); System.out.printf("Service: %10s (%2.2f Tax) Customer Name: %-20s\n", service.getName(), service.getTax(), order.getCustomerName()); System.out.println("-----------------------------------------------------------------------------------------------------------------"); System.out.printf("%25s | %10s | %10s | %10s | %15s | %10s | %10s\n", "Food Item", "Calories", "Packing", "Price", "Packing Price", "Discount %", "Total Price"); System.out.println("-----------------------------------------------------------------------------------------------------------------"); DoubleAdder itemTotalPrice = new DoubleAdder(); order.getFoodItems().stream().forEach(item -> { String name = item.name(); int calories = item.calories(); Packing packing = item.packing(); double price = item.price(); double packingPrice = packing.packingPrice(); double discount = item instanceof Meal? ((Meal)item).discount() : 0.0d; double totalItemPrice = calculateTotalItemPrice(price, packingPrice, discount); System.out.printf("%25s | %10d | %10s | %10.2f | %15.2f | %10.2f | %10.2f\n", name, calories, packing.pack(), price, packing.packingPrice(), discount, totalItemPrice); itemTotalPrice.add(totalItemPrice); }); System.out.println("================================================================================================================="); double billTotal = itemTotalPrice.doubleValue(); billTotal = applyTaxes(billTotal, service); System.out.printf("Date: %-30s %66s %.2f\n", dtf.format(LocalDateTime.now()), "Total Bill (incl. taxes):", billTotal); System.out.println("Enjoy your meal!\n\n\n\n"); } private static double applyTaxes(double billTotal, OrderService service) { return billTotal + (billTotal * service.getTax())/100; } private static double calculateTotalItemPrice(double price, double packingPrice, double discount) { if (discount > 0.0d) { price = price - (price * discount)/100; } return price + packingPrice; } } package org.trishinfotech.builder;
import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import org.trishinfotech.builder.meal.FoodItem; public class Order { private List<FoodItem> foodItems = new ArrayList<FoodItem>(); private String customerName; private OrderService service; public Order(OrderService service, List<FoodItem> foodItems, String customerName) { super(); if (Objects.isNull(service)) { throw new IllegalArgumentException( "Meal can't be order without selecting service 'Take Away' or 'Eat Here'"); } if (Objects.isNull(foodItems) || foodItems.stream().filter(Objects::nonNull).collect(Collectors.toList()).isEmpty()) { throw new IllegalArgumentException( "Meal can't be order without any food item"); } this.service = service; this.foodItems = new ArrayList<FoodItem>(foodItems); this.customerName = customerName; if (this.customerName == null) { this.customerName = "NO NAME"; } } public List<FoodItem> getFoodItems() { return foodItems; } public String getCustomerName() { return customerName; } public OrderService getService() { return service; } } package org.trishinfotech.builder;
import java.util.ArrayList; import java.util.List; import org.trishinfotech.builder.beverages.BeverageSize; import org.trishinfotech.builder.beverages.cold.CocaCola; import org.trishinfotech.builder.beverages.cold.Pepsi; import org.trishinfotech.builder.food.burger.ChickenBurger; import org.trishinfotech.builder.food.burger.VegBurger; import org.trishinfotech.builder.food.nuggets.CheeseNuggets; import org.trishinfotech.builder.food.nuggets.ChickenNuggets; import org.trishinfotech.builder.meal.FoodItem; import org.trishinfotech.builder.meal.Meal; public class OrderBuilder { protected static final double HAPPY_MENU_DISCOUNT = 5.0d; private String customerName; private OrderService service = OrderService.TAKE_AWAY; private List<FoodItem> items = new ArrayList<FoodItem>(); public OrderBuilder() { super(); } // Сеттеры для каждого поля в целевом объекте. В этом примере это Order. // Возвращаемым типом у нас будет сам Builder (например, OrderBuilder), чтобы сделать возможным цепной вызов сеттеров. public OrderBuilder name(String customerName) { this.customerName = customerName; return this; } public OrderBuilder service(OrderService service) { if (service != null) { this.service = service; } return this; } public OrderBuilder item(FoodItem item) { items.add(item); return this; } // Комбо предложения public OrderBuilder vegNuggetsHappyMeal() { List<FoodItem> foodItems = new ArrayList<FoodItem>(); foodItems.add(new CheeseNuggets()); foodItems.add(new Pepsi(BeverageSize.S)); Meal meal = new Meal("Veg Nuggets Happy Meal", foodItems, HAPPY_MENU_DISCOUNT); return item(meal); } public OrderBuilder chickenNuggetsHappyMeal() { List<FoodItem> foodItems = new ArrayList<FoodItem>(); foodItems.add(new ChickenNuggets()); foodItems.add(new CocaCola(BeverageSize.S)); Meal meal = new Meal("Chicken Nuggets Happy Meal", foodItems, HAPPY_MENU_DISCOUNT); return item(meal); } public OrderBuilder vegBurgerHappyMeal() { List<FoodItem> foodItems = new ArrayList<FoodItem>(); foodItems.add(new VegBurger()); foodItems.add(new Pepsi(BeverageSize.S)); Meal meal = new Meal("Veg Burger Happy Meal", foodItems, HAPPY_MENU_DISCOUNT); return item(meal); } public OrderBuilder chickenBurgerHappyMeal() { List<FoodItem> foodItems = new ArrayList<FoodItem>(); foodItems.add(new ChickenBurger()); foodItems.add(new CocaCola(BeverageSize.S)); Meal meal = new Meal("Chicken Burger Happy Meal", foodItems, HAPPY_MENU_DISCOUNT); return item(meal); } public Order build() { Order order = new Order(service, items, customerName); if (!validateOrder()) { System.out.println("Sorry! Order can't be placed without service type (Take Away/Eat Here) and any food item."); return null; } return order; } private boolean validateOrder() { return (service != null) && !items.isEmpty(); } } package org.trishinfotech.builder;
import org.trishinfotech.builder.beverages.BeverageSize; import org.trishinfotech.builder.beverages.cold.CocaCola; import org.trishinfotech.builder.beverages.cold.Pepsi; import org.trishinfotech.builder.beverages.hot.HotChocolate; import org.trishinfotech.builder.food.burger.ChickenBurger; import org.trishinfotech.builder.food.nuggets.CheeseNuggets; import org.trishinfotech.builder.food.nuggets.ChickenNuggets; import org.trishinfotech.builder.util.BillPrinter; public class Main { public static void main(String[] args) { OrderBuilder builder1 = new OrderBuilder(); // you can see the use of chained calls of setters here. No statement terminator // till we set all the values of the object Order meal1 = builder1.name("Brijesh").service(OrderService.TAKE_AWAY).item(new ChickenBurger()) .item(new Pepsi(BeverageSize.M)).vegNuggetsHappyMeal().build(); BillPrinter.printItemisedBill(meal1); OrderBuilder builder2 = new OrderBuilder(); Order meal2 = builder2.name("Micheal").service(OrderService.EAT_HERE).item(new ChickenNuggets()) .item(new CheeseNuggets()).item(new CocaCola(BeverageSize.L)).chickenBurgerHappyMeal() .item(new HotChocolate(BeverageSize.M)).vegBurgerHappyMeal().build(); BillPrinter.printItemisedBill(meal2); } } Food Court
================================================================================================================= Service: Take Away (2.00 Tax) Customer Name: Brijesh ----------------------------------------------------------------------------------------------------------------- Food Item | Calories | Packing | Price | Packing Price | Discount % | Total Price ----------------------------------------------------------------------------------------------------------------- Chicken Burger | 300 | Wrap | 4.50 | 0.40 | 0.00 | 4.90 Pepsi (M) | 220 | Bottle | 2.20 | 0.75 | 0.00 | 2.95 Veg Nuggets Happy Meal | 490 | Multi-Pack | 5.00 | 2.00 | 5.00 | 6.75 ================================================================================================================= Date: 2020/10/09 20:02:38 Total Bill (incl. taxes): 14.89 Enjoy your meal! Food Court ================================================================================================================= Service: Eat Here (5.50 Tax) Customer Name: Micheal ----------------------------------------------------------------------------------------------------------------- Food Item | Calories | Packing | Price | Packing Price | Discount % | Total Price ----------------------------------------------------------------------------------------------------------------- Chicken Nuggets | 450 | Container | 5.00 | 1.25 | 0.00 | 6.25 Cheese Nuggets | 330 | Container | 3.80 | 1.25 | 0.00 | 5.05 Coca-Cola (L) | 290 | Bottle | 2.00 | 0.75 | 0.00 | 2.75 Chicken Burger Happy Meal | 450 | Multi-Pack | 5.50 | 1.15 | 5.00 | 6.38 Hot Chocolate (M) | 450 | Sipper Mug | 2.30 | 1.60 | 0.00 | 3.90 Veg Burger Happy Meal | 340 | Multi-Pack | 3.90 | 1.15 | 5.00 | 4.86 ================================================================================================================= Date: 2020/10/09 20:02:38 Total Bill (incl. taxes): 30.78 Enjoy your meal! Узнать подробнее о курсе «Архитектура и шаблоны проектирования». Смотреть вебинар «Шаблоны GRASP».
=========== Источник: habr.com =========== =========== Автор оригинала: Brijesh Saxena ===========Похожие новости:
Блог компании OTUS ), #_programmirovanie ( Программирование ), #_java, #_proektirovanie_i_refaktoring ( Проектирование и рефакторинг ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 27-Апр 05:21
Часовой пояс: UTC + 5