Java: Строитель

Builder Строитель Builder

Строитель — это порождающий паттерн проектирования, который позволяет создавать объекты пошагово.

В отличие от других порождающих паттернов, Строитель позволяет производить различные продукты, используя один и тот же процесс строительства.

Подробней о Строителе

Особенности паттерна в Java

Сложность:

Популярность: Большая

Применимость: Паттерн можно часто встретить в Java-коде, особенно там, где требуется пошаговое создание продуктов или конфигурация сложных объектов.

Паттерн широко используется в стандартных библиотеках Java:

Признаки применения паттерна: Строителя можно узнать в классе, который имеет один создающий метод и несколько методов настройки создаваемого продукта. Обычно, методы настройки вызывают для удобства цепочкой (например, someBuilder->setValueA(1)->setValueB(2)->create()).

Пример: Пошаговое производство автомобилей

В этом примере Строитель позволяет пошагово конструировать различные конфигурации автомобилей.

Кроме того, здесь показано как с помощью Строителя может создавать другой продукт на основе тех же шагов строительства. Для этого мы имеем два конкретных строителя — CarBuilder и CarManualBuilder.

Порядок строительства продуктов заключён в Директоре. Он знает какие шаги строителей нужно вызвать, чтобы получить ту или иную конфигурацию продукта. Он работает со строителями только через общий интерфейс, что позволяет взаимозаменять объекты строителей для получения разных продуктов.

builders

builders/Builder.java: Общий интерфейс строителей

package refactoring_guru.patterns.builder.example.builders;

import refactoring_guru.patterns.builder.example.cars.Type;
import refactoring_guru.patterns.builder.example.components.*;

/**
 * Интерфейс Строителя объявляет все возможные этапы и шаги
 * конфигурации продукта.
 */
public interface Builder {
    public void setType(Type type);
    public void setSeats(int seats);
    public void setEngine (Engine engine);
    public void setTransmission(Transmission transmission);
    public void setTripComputer(TripComputer tripComputer);
    public void setGPSNavigator(GPSNavigator gpsNavigator);
}

builders/CarBuilder.java: Строитель автомобилей

package refactoring_guru.patterns.builder.example.builders;

import refactoring_guru.patterns.builder.example.cars.Car;
import refactoring_guru.patterns.builder.example.cars.Type;
import refactoring_guru.patterns.builder.example.components.*;

/**
 * Конкретные строители реализуют шаги, объявленные в общем интерфейсе.
 */
public class CarBuilder implements Builder {
    private Type type;
    private int seats;
    private Engine engine;
    private Transmission transmission;
    private TripComputer tripComputer;
    private GPSNavigator gpsNavigator;

    @Override
    public void setType(Type type) {
        this.type = type;
    }

    @Override
    public void setSeats(int seats) {
        this.seats = seats;
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setTransmission(Transmission transmission) {
        this.transmission = transmission;
    }

    @Override
    public void setTripComputer(TripComputer tripComputer) {
        this.tripComputer = tripComputer;
    }

    @Override
    public void setGPSNavigator(GPSNavigator gpsNavigator) {
        this.gpsNavigator = gpsNavigator;
    }

    public Car getResult() {
        return new Car(type, seats, engine, transmission, tripComputer, gpsNavigator);
    }
}

builders/CarManualBuilder.java: Строитель руководств пользователя

package refactoring_guru.patterns.builder.example.builders;

import refactoring_guru.patterns.builder.example.cars.Manual;
import refactoring_guru.patterns.builder.example.cars.Type;
import refactoring_guru.patterns.builder.example.components.*;

/**
 * В отличие от других создающих паттернов, Строители могут создавать совершенно
 * разные продукты, не имеющие общего интерфейса.
 * 
 * В данном случае мы производим руководство пользователя автомобиля с помощью
 * тех же шагов, что и сами автомобили. Это устройство позволит создавать
 * руководства под конкретные модели автомобилей, содержащие те или иные фичи.
 */
public class CarManualBuilder implements Builder{
    private Type type;
    private int seats;
    private Engine engine;
    private Transmission transmission;
    private TripComputer tripComputer;
    private GPSNavigator gpsNavigator;

    @Override
    public void setType(Type type) {
        this.type = type;
    }

    @Override
    public void setSeats(int seats) {
        this.seats = seats;
    }

    @Override
    public void setEngine(Engine engine) {
        this.engine = engine;
    }

    @Override
    public void setTransmission(Transmission transmission) {
        this.transmission = transmission;
    }

    @Override
    public void setTripComputer(TripComputer tripComputer) {
        this.tripComputer = tripComputer;
    }

    @Override
    public void setGPSNavigator(GPSNavigator gpsNavigator) {
        this.gpsNavigator = gpsNavigator;
    }

    public Manual getResult() {
        return new Manual(type, seats, engine, transmission, tripComputer, gpsNavigator);
    }
}

cars

cars/Car.java: Продукт-автомобиль

package refactoring_guru.patterns.builder.example.cars;

import refactoring_guru.patterns.builder.example.components.*;

/**
 * Автомобиль — это класс продукта.
 */
public class Car {
    private final Type type;
    private final int seats;
    private final Engine engine;
    private final Transmission transmission;
    private final TripComputer tripComputer;
    private final GPSNavigator gpsNavigator;
    private static double fuel = 0;

    public Car(Type type, int seats, Engine engine, Transmission transmission,
               TripComputer tripComputer, GPSNavigator gpsNavigator) {
        this.type = type;
        this.seats = seats;
        this.engine = engine;
        this.transmission = transmission;
        this.tripComputer = tripComputer;
        this.gpsNavigator = gpsNavigator;
    }

    public void setFuel(double fuel) {
        this.fuel = fuel;
    }

    public Type getType() {
        return type;
    }

    public int getSeats() {
        return seats;
    }

    public Engine getEngine() {
        return engine;
    }

    public Transmission getTransmission() {
        return transmission;
    }

    public TripComputer getTripComputer() {
        return tripComputer;
    }

    public GPSNavigator getGpsNavigator() {
        return gpsNavigator;
    }

    public static double getFuel() {
        return fuel;
    }
}

cars/Manual.java: Продукт-руководство

package refactoring_guru.patterns.builder.example.cars;

import refactoring_guru.patterns.builder.example.components.*;

/**
 * Руководство автомобиля — это второй продукт. Заметьте, что руководство и сам
 * автомобиль не имеют общего родительского класса. По сути, они независимы.
 */
public class Manual {
    private final Type type;
    private final int seats;
    private final Engine engine;
    private final Transmission transmission;
    private final TripComputer tripComputer;
    private final GPSNavigator gpsNavigator;

    public Manual(Type type, int seats, Engine engine, Transmission transmission,
               TripComputer tripComputer, GPSNavigator gpsNavigator) {
        this.type = type;
        this.seats = seats;
        this.engine = engine;
        this.transmission = transmission;
        this.tripComputer = tripComputer;
        this.gpsNavigator = gpsNavigator;
    }

    public String print() {
        StringBuilder info = new StringBuilder();
        info.append("Type of car: " + type + "\n");
        info.append("Count of seats: " + seats + "\n");
        info.append("Engine: volume - " + engine.getVolume() + "; mileage - " + engine.getMileage() + "\n");
        info.append("Transmission: " + transmission + "\n");
        info.append("Trip Computer: Trip Computer" + "\n");
        info.append("GPS Navigator: GPS Navigator" + "\n");
        return info.toString();
    }
}

cars/Type.java

package refactoring_guru.patterns.builder.example.cars;

public enum Type {
    CITY_CAR, SPORTS_CAR, SUV;
}

components

components/Engine.java: Разные фичи продуктов

package refactoring_guru.patterns.builder.example.components;

/**
 * Одна из фишек автомобиля.
 */
public class Engine {
    private final double volume;
    private double mileage;
    private static boolean started;

    public Engine(double volume, double mileage) {
        this.volume = volume;
        this.mileage = mileage;
    }

    public void on() {
        this.started = true;
    }

    public void of() {
        this.started = false;
    }

    public static boolean isStarted() {
        return started;
    }

    public void go(double mileage) {
        if (started) {
            this.mileage += mileage;
        } else {
            System.err.println("Cannot go(), you must start engine first!");
        }
    }

    public double getVolume() {
        return volume;
    }

    public double getMileage() {
        return mileage;
    }
}

components/GPSNavigator.java: Разные фичи продуктов

package refactoring_guru.patterns.builder.example.components;

/**
 * Одна из фишек автомобиля.
 */
public class GPSNavigator {
    private String route;

    public GPSNavigator() {
        this.route = "221b, Baker Street, London  to Scotland Yard, 8-10 Broadway, London";
    }

    public GPSNavigator(String manualRoute) {
        this.route = manualRoute;
    }

    public String getRoute() {
        return route;
    }
}

components/Transmission.java: Разные фичи продуктов

package refactoring_guru.patterns.builder.example.components;

/**
 * Одна из фишек автомобиля.
 */
public enum Transmission {
    SINGLE_SPEED, MANUAL, AUTOMATIC, SEMI_AUTOMATIC;
}

components/TripComputer.java: Разные фичи продуктов

package refactoring_guru.patterns.builder.example.components;

import refactoring_guru.patterns.builder.example.cars.Car;

/**
 * Одна из фишек автомобиля.
 */
public class TripComputer {

    public void condition() {
        if (Engine.isStarted()) {
            System.out.println("Car is started");
        } else {
            System.out.println("Car isn't started");
        }
    }

    public void fuelLevel() {
        System.out.println("Level of fuel - " + Car.getFuel());
    }
}

director

director/Director.java: Директор управляет строителями

package refactoring_guru.patterns.builder.example.director;

import refactoring_guru.patterns.builder.example.builders.Builder;
import refactoring_guru.patterns.builder.example.cars.Type;
import refactoring_guru.patterns.builder.example.components.*;

/**
 * Директор знает в какой последовательности заставлять работать строителя. Он
 * работает с ним через общий интерфейс Строителя. Из-за этого, он может не
 * знать какой конкретно продукт сейчас строится.
 */
public class Director {

    public void constructSportsCar(Builder builder) {
        builder.setType(Type.SPORTS_CAR);
        builder.setSeats(2);
        builder.setEngine(new Engine(3.0, 0));
        builder.setTransmission(Transmission.SEMI_AUTOMATIC);
        builder.setTripComputer(new TripComputer());
        builder.setGPSNavigator(new GPSNavigator());
    }

    public void constructCityCar(Builder builder) {
        builder.setType(Type.CITY_CAR);
        builder.setSeats(2);
        builder.setEngine(new Engine(1.2, 0));
        builder.setTransmission(Transmission.AUTOMATIC);
        builder.setTripComputer(new TripComputer());
        builder.setGPSNavigator(new GPSNavigator());
    }

    public void constructSUV(Builder builder) {
        builder.setType(Type.SUV);
        builder.setSeats(4);
        builder.setEngine(new Engine(2.5, 0));
        builder.setTransmission(Transmission.MANUAL);
        builder.setGPSNavigator(new GPSNavigator());
    }
}

Demo.java: Клиентский код

package refactoring_guru.patterns.builder.example;

import refactoring_guru.patterns.builder.example.builders.CarBuilder;
import refactoring_guru.patterns.builder.example.builders.CarManualBuilder;
import refactoring_guru.patterns.builder.example.cars.Car;
import refactoring_guru.patterns.builder.example.cars.Manual;
import refactoring_guru.patterns.builder.example.director.Director;

/**
 * Демо-класс. Здесь всё сводится воедино.
 */
public class Demo {

    public static void main(String[] args) {
        Director director = new Director();

        // Директор получает объект конкретного строителя от клиента
        // (приложения). Приложение само знает какой строитель использовать,
        // чтобы получить нужный продукт.
        CarBuilder builder = new CarBuilder();
        director.constructSportsCar(builder);

        // Готовый продукт возвращает строитель, так как Директор чаще всего не
        // знает и не зависит от конкретных классов строителей и продуктов.
        Car car = builder.getResult();
        System.out.println("Car built:\n" + car.getType());


        CarManualBuilder manualBuilder = new CarManualBuilder();

        // Директор может знать больше одного рецепта строительства.
        director.constructSportsCar(manualBuilder);
        Manual carManual = manualBuilder.getResult();
        System.out.println("\nCar manual built:\n" + carManual.print());
    }

}

OutputDemo.txt: Результаты выполнения

Car built:
SPORTS_CAR

Car manual built:
Type of car: SPORTS_CAR
Count of seats: 2
Engine: volume - 3.0; mileage - 0.0
Transmission: SEMI_AUTOMATIC
Trip Computer: Trip Computer
GPS Navigator: GPS Navigator