본문 바로가기

SW 설계/Design Pattern

[Design Pattern] Creational - 빌더 패턴 (Builder Pattern)

728x90

Spring 을 통해 개발을 해본 개발자라면 너무나 익숙할 Builder 패턴이다. 빌더 패턴은 Factory Method 처럼 최종적인 Product Object 생성을 위한 Creational 패턴이며, 그 Product 을 위해 여러 Part Object 들이 조립되어 생성되는 모습이 특징이다 (Spring 에서는 @Builder 를 사용해 빌더 패턴을 적용시킬 수 있다).

 

 

빌더 패턴 같은 경우 최종적인 Product 를 위해 Builder 들이 생성하게 되는 Part Object 들은 큰 의미가 없다. 그런 부분이 Builder 라는 Interface 를 사용해 Client 가 어떤 파트들이 등장하는지 알 필요 없게 만드는 이유이다. 

 

 

빌더 패턴의 UML

 

 

Builder 패턴에서 최종 Build 된 Product 가 어디에 있어야 하는지에 대해서는 선택권이 있다. Holder 같은 객체를 사용해서 보관해도 되고, Builder Interface 에서 final Product 를 반환하도록 해줄 수 있다 (살펴볼 내용, 중요한 부분은 아님).

 

 

중요한건 Director 는 필요한 Part 들과 최종 Product 를 만들기 위한 단계는 알고 있으나, 각각의 Part Object 들을 만드는 방법은 모른다. 따라서 세부적인 Part 를 만드는 지식을 알고 있는 Builder Interface 를 사용하게 된다 (Concrete Builder 들을 교환하며 사용). Director 는 사용할 Builder 를 Composition 방식(Inheritance 제외 4가지)으로 의존하여 각 빌더들을 사용할 수 있다. 

 

 

 

 

예시

 

 

만약 비행기를 만드는 Engineer 가 있다고 하자. 그리고 Airplane 이라는 최종 Product 를 만드려고 하고, 그 Product 에는 Fighter, Glider Product 가 있다고 하자.  Fighter 와 Glider 는 Airplane 을 상속받고, 각각 다른 Object 로 조립되기 때문에 Concrete Builder 를 만들어야 한다.

 

 

Engineer 는 Director 이며, 자신이 만들어야 하는 Builder 를 가지고 있다. 해당 Builder 를 사용해서 적절한 Airplane 객체를 생성해주고, 필요한 Part Object 를 생성하여 Product 를 반환해줄 수 있다. 이 예제에서는 최종 Prouct 틀 생성은 Builder 에서 해주기 때문에 Builder Class 에서 최종 Product 를 보관한다. 

 

 

public class Airplane {

    protected String customer;
    protected String type;
    protected AirplaneWing wing;
    protected AirplaneSeat seat;

    public Airplane(String customer, String type) {
        this.customer = customer;
        this.type = type;
    }

    public void setWing(AirplaneWing wing) {
        this.wing = wing;
    }

    public void setSeat(AirplaneSeat seat) {
        this.seat = seat;
    }

    public String getType() {
        return this.type;
    }
}

 

 

Airplane 객체는 이처럼 자신에게 맞는 AirplaneWing, AirplaneSeat 등의 Part Object 들이 필요하다. 또한 종류를 명시하기 위한 type field 도 가지고 있다. Fighter 와 Glider 는 모두 같은 형태를 띄므로, Fighter 만 적겠다. 

 

 

public class Fighter extends Airplane {
    public Fighter(String customer, String type) {
        super(customer, type);
    }
}

 

 

이 모든 Product 를 생성해주기 위해 AirplaneBuilder Class 가 Builder 역할체로 등장한다. 최종 Airplane Product 를 보관하며, 자신이 어떤 Builder 이냐에 따라서 반환해야 하는 Product 와 Part Object 들이 모두 다르므로 abstract 하게 구현하였다. 

 

 

public abstract class AirplaneBuilder {

    protected Airplane airplane;
    protected String customer;
    protected String type;

    public Airplane getAirplane() {
        return this.airplane;
    }
    
    public abstract void createNewAirplane(); // 빈 객체 만드는 함수 (customer, type 사용)
    public abstract void buildWings();
    public abstract void buildSeats();

}

 

 

public class FighterBuilder extends AirplaneBuilder { // Object 생성 자체를 Build 에 전담한다

    public FighterBuilder(String customer) {
        super.customer = customer;
        super.type = "FIGHTER";
    }

    @Override
    public void createNewAirplane() { // Builder 에 맞춘 Product 를 준비한다
        super.airplane = new Fighter(super.customer, super.type);
    }

    /*
     Builder 는 해당 Product를 위한 Part들을 빌드하여 제공해준다
    */
    @Override
    public void buildWings() {
        super.airplane.setWing(new FighterWing());
    }

    @Override
    public void buildSeats() {
        super.airplane.setSeat(new FighterSeat());
    }
}

 

 

최종적으로 Engineer 는 오직 받는 요청이 최종적인 Product 를 반환하라는 것이다. 따라서 어떤 Product 인지, 어떤 Part Object 들을 사용하는지는 자신이 가지고 있는 Builder 구현체에게 delegate 하며, 자신은 반환된 Part 들로 Product 를 구성만한다. 그리고 Builder 에 보관중인 (어디든 상관 없음) 최종 Product 객체를 Client 에게 반환해줄 수 있어야 한다 (Client 는 빌더 객체를 가지고 있으면 안되기 때문이다). 이것들이 Director 의 역할이다. 

 

 

public class Engineer {

    private AirplaneBuilder airplaneBuilder;

    public Engineer(AirplaneBuilder airplaneBuilder) {
        // 생성시점에 사용할 Builder가 주입된다
        this.airplaneBuilder = airplaneBuilder;
    }

    public void setAirplaneBuilder(AirplaneBuilder airplaneBuilder) {
        // 원하는 Builder 로 바꿀 수 있다
        this.airplaneBuilder = airplaneBuilder;
    }

    // Client 는 Director 에게 최종 Product 반환만을 요구한다
    public Airplane constructAirplane() {
    
        this.airplaneBuilder.createNewAirplane();
        this.airplaneBuilder.buildWings();
        this.airplaneBuilder.buildSeats();
		
        return this.airplaneBuilder.getAirplane();
    }

}

 

 

이를 통해 다음 Client 에서 간단히 Test 를 해볼 수 있다. 단, 애플리케이션 단에서 Client 는 어떤 Builder 를 사용하는지 등의 의존성은 다른 방식으로 주입이 되었을 것이라고 가정하고, 단순히 main 진행으로 Test 를 한다. 

 

 

public static void main(String [] args){

    // 원래는 Engineer 에게 알아서 주입되었을 것
    // Spring 이 였다면 이 객체들 또한 Bean 관리 중이였을 것
    AirplaneBuilder fighterBuilder = new FighterBuilder("MARK");
    AirplaneBuilder gliderBuilder = new GliderBuilder("JASON");
    
    // 여기가 Client이고, 어떤 방식으로든 Director 또한 사용할 수 있을것이다
    Engineer engineer = new Engineer(fighterBuilder); // 우선 Figter 를 만들고 싶다 
    Airplane figtherPlane = engineer.constructAirplane();
    System.out.println("생성된 비행기 type: " + fighterAirplane.getType());

    // 이번엔 GliderBuilder 를 만들고 싶다
    engineer.setAirplaneBuilder(gliderBuilder);
    Airplane figtherPlane = engineer.constructAirplane();
    System.out.println("생성된 비행기 type: " + fighterAirplane.getType());

}


// 출력 결과:
생성된 비행기 type: FIGHTER
생성된 비행기 type: GLIDER

 

 

위와 같이 Client 는 Director 에만 의존하고 Director 가 어떤 방식으로든 (직접 사용하든, 외부로부터 주입받든) 자신이 사용하는 ConcreteBuilder 를 사용 / 변경해준다면, 원하는 Product 를 쉽게 얻어낼 수 있다. 

 

 

 

정리하며

 

 

Builder 패턴은 막상 정석적인 패턴의 모습을 보면 Spring 에서 쓰던 @Builder 에서 쓰던 모습과는 살짝 다른 것 같다. 이 패턴은 다양한 Product 형태의 Builder 들을 포괄할 수 있었다. 내가 최종적으로 원하는 Product 의 구현체를 위한 Builder 구현체가 필요한 모습이였다. 

 

 

전략 패턴과의 다른 점은 전략 패턴은 Behavioral 패턴인만큼 행위에 대한 Interface 를 만들고 그 행위를 각 구현체가 사용하기 위한 자신만의 구현체를 만들어 사용하는 방식이였는데, Builder 패턴은 생성에만 집중하여 Product 를 위해 여러 Part Object 들을 집합시켜서 최종 결과만 전달해주는 Director Class 가 등장하는 모습이 큰 차이점이다. 

 

 

어떤 모습에서는 Abstract Factory Pattern 과도 비슷하다고 보일 수는 있다. 하지만 빌더 패턴에서의 파트들은 Client 에겐 아무 의미가 없을 때 사용되고, AF Pattern 은 그 Family 의 모든 구성원들이 Client 에게 의미있게 사용될 때 사용된다. 

 

 

Composite 패턴과는 긴밀한 연관이 있는데, Product 처럼 Complex 한 Object 를 사용하는 Representation 을 담당하고, Builder 패턴은 이런 Object 를 생성하는 Construction 담당하는 케이스가 많다고 한다. 

 

 

 

출처

 

 

1) 에릭 프리먼, Head First Design Patterns』, O'Reilly Media (2004)

 

 

2) 중앙대학교 소프트웨어학부 이찬근 교수님 디자인 패턴 수업자료 중

728x90