Builder

chrishan·2021년 5월 9일
0

Design pattern

목록 보기
3/11

Intent

Builder 복잡한 개체를 단계별로 구성 할 수있는 창의적인 디자인 패턴입니다. 패턴을 사용하면 동일한 구성 코드를 사용하여 객체의 다른 유형과 표현을 생성 할 수 있습니다.

Problem

많은 필드와 중첩 된 개체를 힘들게 단계별로 초기화해야하는 복잡한 개체를 상상해보십시오. 이러한 초기화 코드는 일반적으로 많은 매개 변수가있는 괴물 같은 생성자 내부에 묻혀 있습니다. 또는 더 나쁜 것은 클라이언트 코드 전체에 흩어져 있습니다.

예를 들어House 오브젝트를 만드는 방법에 대해 생각해 보겠습니다. 단순한 집을 짓기 위해서는 4 개의 벽과 바닥을 짓고 문을 설치하고 한 쌍의 창문을 맞추고 지붕을 만들어야합니다. 하지만 뒷마당과 다른 물건 (난방 시스템, 배관, 전기 배선 등)이있는 더 크고 밝은 집을 원한다면 어떨까요?

가장 간단한 해결책은 기본House 클래스를 확장하고 매개 변수의 모든 조합을 포함하는 하위 클래스 집합을 만드는 것입니다. 그러나 결국에는 상당한 수의 하위 클래스로 끝날 것입니다. 현관 스타일과 같은 새로운 매개 변수는이 계층 구조를 더욱 확장해야합니다.

육종 하위 클래스를 포함하지 않는 또 다른 접근 방식이 있습니다. 하우스 객체를 제어하는 가능한 모든 매개 변수를 사용하여 기본House 클래스에서 바로 거대한 생성자를 만들 수 있습니다. 이 접근 방식은 실제로 하위 클래스의 필요성을 제거하지만 또 다른 문제를 야기합니다.

대부분의 경우 대부분의 매개 변수가 사용되지 않아 the constructor calls pretty ugly. 예를 들어, 집의 일부에만 수영장이 있으므로 수영장과 관련된 매개 변수는 10 개 중 9 번은 쓸모가 없습니다.

Solution

Builder 패턴은 자체 클래스에서 객체 생성 코드를 추출하여 _builders_라는 별도의 객체로 이동하도록 제안합니다.

패턴은 객체 구성을 일련의 단계 (buildWalls,buildDoor 등)로 구성합니다. 개체를 생성하려면 빌더 개체에서 이러한 일련의 단계를 실행합니다. 중요한 부분은 모든 단계를 호출 할 필요가 없다는 것입니다. 개체의 특정 구성을 생성하는 데 필요한 단계 만 호출 할 수 있습니다.

제품의 다양한 표현을 빌드해야 할 때 일부 구성 단계에는 다른 구현이 필요할 수 있습니다. 예를 들어 오두막의 벽은 나무로 만들 수 있지만 성벽은 돌로 만들어야합니다.

이 경우 동일한 빌드 단계 세트를 다른 방식으로 구현하는 여러 다른 빌더 클래스를 작성할 수 있습니다. 그런 다음 구성 프로세스 (즉, 빌드 단계에 대한 순서가 지정된 호출 집합)에서 이러한 빌더를 사용하여 다양한 종류의 개체를 생성 할 수 있습니다.

예를 들어, 나무와 유리로 모든 것을 건축하는 건축업자, 돌과 철로 모든 것을 건축하는 두 번째 건축, 금과 다이아몬드를 사용하는 건축 건축업자를 상상해보십시오. 동일한 단계를 호출하면 첫 번째 건축가의 일반 집, 두 번째 건물의 작은 성, 세 번째 건물의 궁전을 얻을 수 있습니다. 그러나 이것은 빌드 단계를 호출하는 클라이언트 코드가 공통 인터페이스를 사용하여 빌더와 상호 작용할 수있는 경우에만 작동합니다.

Director

더 나아가서 제품을 디렉터라고하는 별도의 클래스로 구성하는 데 사용하는 빌더 단계에 대한 일련의 호출을 추출 할 수 있습니다. director 클래스는 빌드 단계를 실행하는 순서를 정의하고 빌더는 해당 단계에 대한 구현을 제공합니다.

프로그램에 디렉터 클래스가 반드시 필요한 것은 아닙니다. 항상 클라이언트 코드에서 직접 특정 순서로 빌드 단계를 호출 할 수 있습니다. 그러나 director 클래스는 다양한 구성 루틴을 배치하여 프로그램 전체에서 재사용 할 수있는 좋은 장소가 될 수 있습니다.

또한 director 클래스는 클라이언트 코드에서 제품 구성의 세부 사항을 완전히 숨 깁니다. 클라이언트는 건축업자를 감독과 연결하고, 건축을 감독과 시작하고, 건축업자로부터 결과를 얻기 만하면됩니다.

Structure

  1. Builder 인터페이스는 모든 유형의 빌더에 공통적인 Product 구성 단계를 선언합니다.
  2. Concrete Builder는 생성 단계의 다양한 구현을 제공합니다. Concreate Builder는 공통 인터페이스를 따르지 않는 Product를 생산할 수 있습니다.
  3. Product는 결과 객체입니다. 다른 빌더에 의해 생성된 Product는 동일한 클래스 계층 또는 인터페이스에 속할 필요가 없습니다.
  4. Director 클래스는 생성 단계를 호출하는 순서를 정의하므로 Product의 특정 구성을 만들고 재사용할 수 있습니다.
  5. Client는 빌더 개체 중 하나를 감독과 연결해야 합니다. 일반적으로 director 생성자의 매개변수를 통해 한 번만 수행됩니다. 그런 다음 director는 모든 추가 구성에 해당 빌더 개체를 사용합니다. 그러나 클라이언트가 빌더 개체를 director의 production method에 전달할 때 대체 접근 방식이 있습니다. 이 경우 director와 함께 무언가를 만들 때마다 다른 빌더를 사용할 수 있습니다.

Pseudocode

Builder 패턴의 이 예는 자동차와 같은 다양한 유형의 제품을 제작할 때 동일한 객체 구성 코드를 재사용하고 이에 해당하는 매뉴얼을 만드는 방법을 보여줍니다.

자동차는 수백 가지 방법으로 구성 할 수있는 복잡한 물체입니다. 거대한 생성자로 Car클래스를 부 풀리는 대신 자동차 어셈블리 코드를 별도의 자동차 빌더 클래스로 추출했습니다. 이 클래스에는 자동차의 다양한 부품을 구성하는 일련의 메서드가 있습니다.

클라이언트 코드가 특수하고 미세 조정 된 자동차 모델을 조립해야하는 경우 빌더와 직접 작업 할 수 있습니다. 반면에 클라이언트는 가장 인기있는 자동차 모델을 구성하기 위해 빌더를 사용하는 방법을 알고있는 디렉터 클래스에 어셈블리를 위임 할 수 있습니다.

당신은 충격을받을 수 있지만 모든 차에는 매뉴얼이 필요합니다 (진지하게 누가 그것을 읽습니까?). 설명서는 자동차의 모든 기능을 설명하므로 설명서의 세부 사항은 모델에 따라 다릅니다. 그렇기 때문에 실제 자동차와 해당 설명서 모두에 대해 기존 건설 프로세스를 재사용하는 것이 합리적입니다. 물론 매뉴얼 제작은 자동차 제작과 다르기 때문에 매뉴얼 작성을 전문으로하는 또 다른 빌더 클래스를 제공해야합니다. 이 클래스는 자동차 제작 형제와 동일한 제작 방법을 구현하지만 자동차 부품을 만드는 대신 설명합니다. 이 빌더를 동일한 디렉터 개체에 전달하면 자동차 또는 설명서를 만들 수 있습니다.

마지막 부분은 결과 객체를 가져 오는 것입니다. 금속 자동차와 종이 매뉴얼은 관련이 있지만 여전히 매우 다른 것입니다. 디렉터를 구체적인 제품 클래스에 연결하지 않고 디렉터에서 결과를 가져 오는 방법을 배치 할 수 없습니다. 따라서 우리는 작업을 수행 한 건축업자로부터 시공 결과를 얻습니다.

// Using the Builder pattern makes sense only when your products
// are quite complex and require extensive configuration. The
// following two products are related, although they don't have
// a common interface.
class Car is
    // A car can have a GPS, trip computer and some number of
    // seats. Different models of cars (sports car, SUV,
    // cabriolet) might have different features installed or
    // enabled.

class Manual is
    // Each car should have a user manual that corresponds to
    // the car's configuration and describes all its features.


// The builder interface specifies methods for creating the
// different parts of the product objects.
interface Builder is
    method reset()
    method setSeats(...)
    method setEngine(...)
    method setTripComputer(...)
    method setGPS(...)

// The concrete builder classes follow the builder interface and
// provide specific implementations of the building steps. Your
// program may have several variations of builders, each
// implemented differently.
class CarBuilder implements Builder is
    private field car:Car

    // A fresh builder instance should contain a blank product
    // object which it uses in further assembly.
    constructor CarBuilder() is
        this.reset()

    // The reset method clears the object being built.
    method reset() is
        this.car = new Car()

    // All production steps work with the same product instance.
    method setSeats(...) is
        // Set the number of seats in the car.

    method setEngine(...) is
        // Install a given engine.

    method setTripComputer(...) is
        // Install a trip computer.

    method setGPS(...) is
        // Install a global positioning system.

    // Concrete builders are supposed to provide their own
    // methods for retrieving results. That's because various
    // types of builders may create entirely different products
    // that don't all follow the same interface. Therefore such
    // methods can't be declared in the builder interface (at
    // least not in a statically-typed programming language).
    //
    // Usually, after returning the end result to the client, a
    // builder instance is expected to be ready to start
    // producing another product. That's why it's a usual
    // practice to call the reset method at the end of the
    // `getProduct` method body. However, this behavior isn't
    // mandatory, and you can make your builder wait for an
    // explicit reset call from the client code before disposing
    // of the previous result.
    method getProduct():Car is
        product = this.car
        this.reset()
        return product

// Unlike other creational patterns, builder lets you construct
// products that don't follow the common interface.
class CarManualBuilder implements Builder is
    private field manual:Manual

    constructor CarManualBuilder() is
        this.reset()

    method reset() is
        this.manual = new Manual()

    method setSeats(...) is
        // Document car seat features.

    method setEngine(...) is
        // Add engine instructions.

    method setTripComputer(...) is
        // Add trip computer instructions.

    method setGPS(...) is
        // Add GPS instructions.

    method getProduct():Manual is
        // Return the manual and reset the builder.


// The director is only responsible for executing the building
// steps in a particular sequence. It's helpful when producing
// products according to a specific order or configuration.
// Strictly speaking, the director class is optional, since the
// client can control builders directly.
class Director is
    private field builder:Builder

    // The director works with any builder instance that the
    // client code passes to it. This way, the client code may
    // alter the final type of the newly assembled product.
    method setBuilder(builder:Builder)
        this.builder = builder

    // The director can construct several product variations
    // using the same building steps.
    method constructSportsCar(builder: Builder) is
        builder.reset()
        builder.setSeats(2)
        builder.setEngine(new SportEngine())
        builder.setTripComputer(true)
        builder.setGPS(true)

    method constructSUV(builder: Builder) is
        // ...


// The client code creates a builder object, passes it to the
// director and then initiates the construction process. The end
// result is retrieved from the builder object.
class Application is

    method makeCar() is
        director = new Director()

        CarBuilder builder = new CarBuilder()
        director.constructSportsCar(builder)
        Car car = builder.getProduct()

        CarManualBuilder builder = new CarManualBuilder()
        director.constructSportsCar(builder)

        // The final product is often retrieved from a builder
        // object since the director isn't aware of and not
        // dependent on concrete builders and products.
        Manual manual = builder.getProduct()

Applicability

Use the Builder pattern to get rid of a “telescopic constructor”.
10 개의 선택적 매개 변수가있는 생성자가 있다고 가정합니다. 그런 beast 부르는 것은 매우 불편합니다. 따라서 생성자를 오버로드하고 더 적은 매개 변수로 더 짧은 버전을 여러 개 만듭니다. 이러한 생성자는 여전히 기본 생성자를 참조하여 일부 기본값을 생략 된 매개 변수에 전달합니다.

class Pizza {
    Pizza(int size) { ... }
    Pizza(int size, boolean cheese) { ... }
    Pizza(int size, boolean cheese, boolean pepperoni) { ... }
    // ...

_Creating such a monster is only possible in languages that support method overloading, such as C# or Java._
Builder 패턴을 사용하면 실제로 필요한 단계 만 사용하여 단계별로 개체를 빌드 할 수 있습니다. 패턴을 구현 한 후에는 더 이상 생성자에 수십 개의 매개 변수를 넣을 필요가 없습니다.


Use the Builder pattern when you want your code to be able to create different representations of some product (for example, stone and wooden houses).
빌더 패턴은 제품의 다양한 표현 구성에 세부 사항 만 다른 유사한 단계를 포함 할 때 적용 할 수 있습니다.

기본 빌더 인터페이스는 가능한 모든 구성 단계를 정의하고 구체적인 빌더는 이러한 단계를 구현하여 제품의 특정 표현을 구성합니다. 한편, 디렉터 클래스는 시공 순서를 안내합니다.

Use the Builder to construct Composite trees or other complex objects.
빌더 패턴을 사용하면 제품을 단계별로 구성 할 수 있습니다. 최종 제품을 중단하지 않고 일부 단계의 실행을 연기 할 수 있습니다. 단계를 재귀 적으로 호출 할 수도 있으므로 개체 트리를 구축해야 할 때 유용합니다.

건축업자는 건설 단계를 실행하는 동안 미완성 제품을 노출하지 않습니다. 이것은 클라이언트 코드가 불완전한 결과를 가져 오는 것을 방지합니다.

How to Implement

  1. 사용 가능한 모든 제품 표현을 구축하기위한 공통 구성 단계를 명확하게 정의 할 수 있는지 확인하십시오. 그렇지 않으면 패턴 구현을 진행할 수 없습니다.

  2. 기본 빌더 인터페이스에서 이러한 단계를 선언하십시오.

  3. 각 제품 표현에 대한 구체적인 빌더 클래스를 만들고 해당 구성 단계를 구현합니다.

    구성 결과를 가져 오는 방법을 구현하는 것을 잊지 마십시오. 빌더 인터페이스 내에서이 메소드를 선언 할 수없는 이유는 여러 빌더가 공통 인터페이스가없는 제품을 구성 할 수 있기 때문입니다. 따라서 이러한 메서드의 반환 유형이 무엇인지 알 수 없습니다. 그러나 단일 계층의 제품을 처리하는 경우 가져 오기 방법을 기본 인터페이스에 안전하게 추가 할 수 있습니다.

  4. 디렉터 클래스를 만드는 것에 대해 생각해보십시오. 동일한 빌더 개체를 사용하여 제품을 구성하는 다양한 방법을 캡슐화 할 수 있습니다.

  5. 클라이언트 코드는 빌더와 디렉터 개체를 모두 만듭니다. 건설이 시작되기 전에 클라이언트는 빌더 개체를 감독에게 전달해야합니다. 일반적으로 클라이언트는 감독 생성자의 매개 변수를 통해이 작업을 한 번만 수행합니다. 감독은 모든 추가 구성에서 빌더 개체를 사용합니다. 건축업자가 감독의 시공 방법으로 직접 전달되는 대안적인 접근 방식이 있습니다.

  6. 모든 제품이 동일한 인터페이스를 따르는 경우에만 디렉터로부터 직접 시공 결과를 얻을 수 있습니다. 그렇지 않으면 클라이언트는 빌더에서 결과를 가져와야합니다.

Pros and Cons

O. 객체를 단계별로 구성하거나 구성 단계를 연기하거나 단계를 반복적으로 실행할 수 있습니다.

O. 제품의 다양한 표현을 빌드 할 때 동일한 구성 코드를 재사용 할 수 있습니다.

O. 단일 책임 원칙. 제품의 비즈니스 로직에서 복잡한 구성 코드를 분리 할 수 있습니다.

X. 패턴이 여러 개의 새 클래스를 만들어야하므로 코드의 전체적인 복잡성이 증가합니다.

Relations with Other Patterns

  • 많은 디자인은 Factory Method (less complicated and more customizable via subclasses)사용으로부터 시작합니다. 다음으로 Abstract Factory, Prototype, 또는 Builder로 발전합니다. (more flexible, but more complicated).

  • Builder는 복잡한 개체를 단계별로 구성하는 데 중점을 둡니다.. Abstract Factory 는 관련 개체의 패밀리를 만드는 데 특화되어 있습니다. Abstract Factory는 제품을 즉시 반환하지만 Builder를 사용하면 제품을 가져 오기 전에 몇 가지 추가 구성 단계를 실행할 수 있습니다.

  • 반복적으로 작업하도록 구성 단계를 프로그래밍 할 수 있으므로 복잡한 Composite트리를 만들 때 Builder를 사용할 수 있습니다.

  • BuilderBridge를 결합 할 수 있습니다. director class는 추상화 역할을하고 다른 빌더는 구현 역할을합니다.

  • Abstract Factories, BuildersPrototypes는 모두 Singletons로 구현 할 수 있습니다.

원문: https://refactoring.guru/design-patterns/builder

0개의 댓글