[Программирование, Java, Проектирование и рефакторинг] Паттерн проектирования Builder (Строитель) в Java (перевод)

Автор Сообщение
news_bot ®

Стаж: 6 лет 9 месяцев
Сообщений: 27286

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

А вот и я со своей очередной статьей о паттернах проектирования, а именно о паттерне проектирования Builder (он же Строитель). Очень полезный паттерн проектирования, который позволяет нам шаг за шагом конструировать сложные объекты.Паттерн проектирования Builder 
  • Паттерн проектирования Builder разработан для обеспечения гибкого решения различных задач создания объектов в объектно-ориентированном программировании.
  • Паттерн проектирования Builder позволяет отделить построение сложного объекта от его представления.
  • Паттерн Builder создает сложные объекты, используя простые объекты и поэтапный подход.
  • Паттерн предоставляет один из лучших способов создания сложных объектов.
  • Этот паттерн полезен для создания разных иммутабельных объектов с помощью одного и того же процесса построения объекта.
Паттерн 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
===========
Похожие новости: Теги для поиска: #_programmirovanie (Программирование), #_java, #_proektirovanie_i_refaktoring (Проектирование и рефакторинг), #_java, #_grasp, #_arhitektura_po (архитектура по), #_patterny_proektirovanija (паттерны проектирования), #_blog_kompanii_otus (
Блог компании OTUS
)
, #_programmirovanie (
Программирование
)
, #_java, #_proektirovanie_i_refaktoring (
Проектирование и рефакторинг
)
Профиль  ЛС 
Показать сообщения:     

Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы

Текущее время: 22-Ноя 12:52
Часовой пояс: UTC + 5