Строитель - Паттерн разработки

 

Определение

 

Как известно, шаблон Фабрика создает объект одного из нескольких подклассов в зависимости от полученных параметров. Но часто объекты могут быть сложными и их создание требует выполнения целого набора операций по их "сборке" из простых объектов. При этом может потребоваться использовать разные реализации этих простых объектов или алгоритм сборки целевого объекта может быть различным. Для отделения построения объекта от деталей его конструкции применяется шаблон Строитель (Builder).

 

В шаблоне Строитель мы инкапсулируем, скрываем алгоритм сборки объекта и выбора реализаций его частей внутри класса конкретного строителя, реализующего базовый интерфейс строителя. В зависимости от того, какой именно алгоритм сборки нам требуется, мы выбираем конкретного строителя. Инициализация строителя, вызов его методов и получение целевого объекта обычно описывают в классе Директора (Director).

 

Когда используется и в чем его достоинства

  • Позволяет изменять внутреннее представление конечного продукта.
  • Скрывает детали строительства.
  • Каждый конкретный строитель независим от других строителей и остальной части кода: проще добавлять новых строителей, выше модульность вашего приложения.
  • Поскольку строитель собирает конечный продукт шаг за шагом, вы лучше контролируете каждый из продуктов.

Строитель похож на абстрактную фабрику тем, что также возвращает классы, использующие наборы методов и объектов. Но главное отличие строителя от абстрактной фабрики в том, что строитель собирает объект поэтапно.

 

Пример

 

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

 

package org.test.technerium.patterns.builder;

public class House {
    private int stores; //количество этажей
    public House(){
        stores = 0; //дом не построен, 0 этажей
    }
    public void setStores(int newLevel){ //в процессе строительства мы будем увеличивать количество этажей
        log("setting stores to: " + newLevel);
        stores = newLevel;
    }
    public int getStores(){
        return stores;
    }
    public void buildBase(){//Построить фундамент
        log("Build base");
    }
    public void buildFloor(){//Построить пол
        log("Build floor");
    }
    public void buildRoof(){//Построить крышу
        log("Build roof");
    }

    public void buildWalls(Window window){// Построить стены
        log("Build walls with window: " + window.getWindowType());
    }
    private void log(String msg){
        System.out.println(msg);
    }
}

Как видно из примера, мы имеем класс с набором методов, каждый из которых может быть использован в процессе сборки объекта. Нам понадобится метод для строительства фундамента, а также стен, крыши и т.п. Метод для строительства стен требует передачи объекта типа Window, и для разных типов домов мы должны использовать разные типы окон, реализующих базовый интерфейс Window:

 

package org.test.technerium.patterns.builder;

public interface Window {
    public String getWindowType();
}

Это может быть простое окно SimpleWindow:

 

package org.test.technerium.patterns.builder;

public class SimpleWindow implements Window{

    @Override
    public String getWindowType() {
        return "Simple window";
    }
    
}

 

Или сложное ComplexWindow:

 

package org.test.technerium.patterns.builder;

public class ComplexWindow implements Window{

    @Override
    public String getWindowType() {
        return "Complex window";
    }
    
}

 

Что ж, теперь настало время написать интерфейс для нашего строителя в абстрактном классе Builder:

package org.test.technerium.patterns.builder;

public abstract class Builder {
    protected House house;
    
    public abstract House buildHouse();
}

 

Нам будет необходимо реализовать весь алгоритм сборки дома внутри метода buildHouse.

Далее пишем строителя простого одноэтажного дома RanchBuilderImpl:

package org.test.technerium.patterns.builder;

public class RanchBuilderImpl extends Builder{
    public RanchBuilderImpl(){
        house = new House();
    }
    
    @Override
    public House buildHouse() {
        house.buildBase(); //фундамент
        house.buildFloor(); //пол
        Window simpleWindow = new SimpleWindow(); //инициализируем простое окно
        house.buildWalls(simpleWindow); //строим стены
        house.buildRoof(); //крыша
        house.setStores(1); //готов 1 этаж
        return house; //Возвращаем собранный объект
    }

}

Здесь мы строим одноэтажный дом, используя именно то окно, которое нам нужно сейчас и вызывая методы согласно конкретному алгоритму.

 

Далее напишем строителя двухэтажного дома MultiStoreyBuilderImpl:

 

package org.test.technerium.patterns.builder;

public class MultiStoreyBuilderImpl extends Builder{
    public MultiStoreyBuilderImpl(){
        house = new House();
    }
    
    @Override
    public House buildHouse() {
        house.buildBase();//фундамент
        house.buildFloor();//пол 1 этажа
        Window complexWindow = new ComplexWindow();
        house.buildWalls(complexWindow); //стены
        house.buildFloor(); //пол второго этажа
        house.setStores(1); //1 этаж готов
        complexWindow = new ComplexWindow();
        house.buildWalls(complexWindow); //стены
        house.buildRoof(); //крыша
        house.setStores(2); //2 этаж готов
        return house; //возвращаем готовый объект
    }
}

А здесь мы используем другую реализацию окна и другой алгоритм.

Всё, теперь пишем директора для управления строителем:

package org.test.technerium.patterns.builder;

public class Director {
    private Builder builder;
    public Director(int level){
        if(level > 1)//Выбираем строителя
            builder = new MultiStoreyBuilderImpl();
        else
            builder = new RanchBuilderImpl();
    }
    
    public House buildHouse(){
        return builder.buildHouse(); //заставляем строителя строить нам дом
    }
}

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

 

Ну для запуска нашего теста пишем последний класс:

package org.test.technerium.patterns.builder;

import java.util.logging.Level;
import java.util.logging.Logger;

public class RunTest {
    public static void main(String[] args){
        log("building ranch house--->");
        Director director = new Director(1);
        House house = director.buildHouse();
        log("building multistorey house--->");
        director = new Director(2);
        house = director.buildHouse();
    }
    private static void log(String msg){
        System.out.println(msg);
    }
}

То есть сначала строим одноэтажный дом, а затем двухэтажный

Запускаем приложение и в консоли видим процесс строительства:

 

building ranch house--->
Build base
Build floor
Build walls with window: Simple window
Build roof
setting stores to: 1
building multistorey house--->
Build base
Build floor
Build walls with window: Complex window
Build floor
setting stores to: 1
Build walls with window: Complex window
Build roof
setting stores to: 2

 


 

Надеюсь, теперь вы получили представление о паттерне Строитель на примере Java. Все примеры в архиве проекта для Eclipse можно скачать по ссылке.

Возможно, вам будут интересны и другие шаблоны, представленные в обзорной статье

 

Была ли статья полезна: 

Комментарии

Отличное описание, хорошие примеры, большое спасибо.

Рад, что вам понравилось. Комментарии мотивируют чаще заглядывать на сайт и выкладывать еще что-нибудь интересное и полезное.

Добавить комментарий

HTML

  • Разрешённые HTML-теги: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <pre> <p>
  • Строки и параграфы переносятся автоматически.
  • Адреса страниц и электронной почты автоматически преобразуются в ссылки.
  • Поместите примеры вашего исходного кода в теги <code>...</code> or <source>...</source> и он будет красиво отформатирован.

Plain text

  • Поместите примеры вашего исходного кода в теги <code>...</code> or <source>...</source> и он будет красиво отформатирован.
  • Строки и параграфы переносятся автоматически.
  • Разрешённые HTML-теги: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd> <code> <source>
CAPTCHA
Пожалуйста, подтвердите, что вы человек.