본문 바로가기
더 좋은 개발자 되기/개발자 책읽기

[개발자 책읽기] Java로 해석한 GoF 의 Design Pattern (생성 - 빌더 패턴)

by Wonit 2022. 3. 12.

해당 글은 Gang of Four의 디자인 패턴 이라는 책을 읽고 학습한 내용을 정리 및 회고하는 글 입니다. 자세한 사항은 YES 24 GoF의 디자인 패턴 에서 확인해주세요.

 

GoF 의 디자인 패턴 - 재사용성을 지닌 객체지향 소프트웨어의 핵심 요소

 

  • 프로텍 미디어
  • 지은이: 에릭 감마, 존 블리사이드스, 리처드 헬름, 랄프 존슨
  • 옮긴이: 김정아

 

 


 

Builder Pattern, 빌더 패턴

 

빌더 패턴은 생성 패턴중 하나로, 복잡한 객체를 생성하는 방법을 단계별로 나눌 수 있도록 도와주는 패턴이다. 빌더 패턴을 통해서 동일한 코드로 다양한 성질을 띄는 하나의 객체를 생성할 수 있다.

  • 복잡한 객체를 생성하는 방법과 표현하는 방법을 정의하는 클래스를 별도로 분리한다.
  • 서로 다른 방법으로 생성이 되었더라도 동일한 절차를 제공할 수 있도록 한다.

 

동기

 

만약 한 객체를 생성한다고 했을 때, 이 객체를 생성하기 위해서 여러 객체를 필요로 한다고 해보자.

 

예를 들어서 쇼핑몰 시스템에서 물품을 등록할 때 할인 기간, 적용 이벤트, 구매 권한 등등 이 필요하고 각각의 항목들이 있을 때와 없을 때 물품의 표현이 달라져야 한다면 어떻게 될까?

 

즉, 한 객체를 생성하기 위해서 생성자에 여러 조건이 있다고 한다면 객체를 생성한다는것 자체만으로도 꽤나 번거로운 일이 될 것이다.

 

이러한 경우 다양한 Builder 클래스 를 통해서 여러 개의 다른 생성 클래스

 

구현

 

참여 객체

 

  • Builder
    • Product 의 생성 단계를 선언해주는 인터페이스로 모든 Builder 의 추상 객체이다
      • ItemBuilder
  • Director
    • 특정 Product 를 생성할 수 있도록 구성된 단계를 호출하는 순서를 정의한다
      • Director
  • ConcreteBuilder
    • 서로 다른 단계에 따라 객체를 생성할 Builder 의 구현체이다
      • NotebookBuilder
  • Product
    • 결과 객체로 서로 다른 Builder 에 의해서 생성된다.
      • Notebook
    • 이외에도 Product 를 만들기 위해서 필요한 객체들을 나열해보자면
      • CPU 인터페이스와 구현체인 Intel, M1
      • OS 인터페이스와 구현체인 OSX, Windows
      • GraphicCard 인터페이스와 구현체인 GTX1080, Radeon

Builder - ItemBuilder Interface

 

Builder 인터페이스는 Product 생성에 필요한 Step 들을 표현할 수 있다.

 

Step 이라고 함은 각각의 Product 를 생성하기 위한 구성들을 선언한다는 의미이다.

 

public interface ItemBuilder {
    void graphicCard(GraphicCard part);
    void cpu(CPU part);
    void os(OS os);
}

 

ConcreteBuilder - Desktop & Notebook Builder class

 

실제 Builder 의 구현체로 현재는 setter 를 사용하여 Notebook 객체에 레퍼런스들을 전달해준다.

 

public class NotebookBuilder implements ItemBuilder {

    private Notebook notebook = new Notebook();

    public Notebook getProduct() {
        return notebook;
    }

    @Override
    public void graphicCard(GraphicCard graphicCard) {
        notebook.setGraphicCard(graphicCard);
    }

    @Override
    public void cpu(CPU cpu) {
        notebook.setCpu(cpu);
    }

    @Override
    public void os(OS os) {
        notebook.setOs(os);
    }
}

 

Product - Noteboo class 와 각각의 부품들

 

Product 는 실제 Director 에 의해서 생성될 대상인데, 객체 생성의 복잡성을 일부 표현하기 위해서 여러 인터페이스와 각각의 구현체를 추가해볼 생각이다.

 

이들은 Notebook 을 생성하기 위해서 필요한 부품들이다.

 

public interface CPU {
    String describe();
}

public interface GraphicCard {
    String describe();
}

public interface OS {
    String describe();
}

public class Intel implements CPU {
    @Override
    public String describe() {
        return "Intel CPU";
    }
}

public class M1 implements CPU {
    @Override
    public String describe() {
        return "M1 CPU";
    }
}

public class GTX1080 implements GraphicCard {
    @Override
    public String describe() {
        return "GTX1080 Graphic Card";
    }
}

public class Radeon implements GraphicCard {
    @Override
    public String describe() {
        return "Radeon Graphic Card";
    }
}

public class OSX implements OS {
    @Override
    public String describe() {
        return "Apple's OSX";
    }
}

public class Windows implements OS {
    @Override
    public String describe() {
        return "MS's Windows";
    }
}

 

이제 실제로 우리가 원하는 Product 인 객체를 정의해보자.

 

public class Notebook {
    private CPU cpu;
    private GraphicCard graphicCard;
    private OS os;

    public void setCpu(CPU cpu) {
        this.cpu = cpu;
    }

    public void setGraphicCard(GraphicCard graphicCard) {
        this.graphicCard = graphicCard;
    }

    public void setOs(OS os) {
        this.os = os;
    }

    @Override
    public String toString() {
        return "{" +
                "cpu=[" + cpu.describe() + "] " +
                ", graphicCard=[" + graphicCard.describe() + "] " +
                ", os=[" + os.describe() + "] " +
                "} 입니다.";
    }
}

 

Director - Director Class

 

Director 는 실제로 Builder 의 구현체들을 통해서 어떤 순서로, 어떤 방식으로 Product 를 생성할지 명시해주고 생성하는 책임을 갖는다.

 

public class Director {
    public Notebook macBook2022(NotebookBuilder builder) {
        builder.cpu(new M1());
        builder.graphicCard(new Radeon());
        builder.os(new OSX());
        return builder.getProduct();
    }

    public Notebook macBook2021(NotebookBuilder builder) {
        builder.cpu(new Intel());
        builder.graphicCard(new Radeon());
        builder.os(new OSX());
        return builder.getProduct();
    }

    public Notebook samsungGalaxyBook(NotebookBuilder builder) {
        builder.cpu(new Intel());
        builder.graphicCard(new GTX1080());
        builder.os(new Windows());
        return builder.getProduct();
    }
}

 

위의 코드를 보게 된다면, Builder 를 통해서 하나의 객체를 다양한 방법을 통해서 생성할 수 있고, 이들을 각각 다른 방식으로 표현할 수 있게 된다.

 

장점과 단점

 

장점

  • 객체를 step-by-step 으로 생성할 수 있도록 도와준다.
  • 객체를 생성하는 표현을 통일시켜준다
  • 복잡한 생성 과정을 분리함으로 SRP 를 만족시킬 수 있게 된다

 

단점

  • 여러 개의 새로운 클래스를 생성해야 하기 때문에 시스템의 복잡성이 증가한다

 

정리

Builder 패턴은 복잡한 객체를 단계별로 구성함에 중점을 두고 있다.

이와 같은 맥락으로는 Effective Java 에서 소개된 Builder 즉, Spring 진영에서 많이 사용되는 Lombok 의 Builder 과 맥락은 공유하고 있다.

하지만 Effective Java 와 Gof 에서 말하는 Builder 는 조금 다르다.

 

 

  • Effective Java's Builder
    • 점증적 생성자 패턴
    • 변경 불가능성, 객체 일관성, 코드 가독성 중점
    • 코딩 스타일에 중점을 둠
  • GoF's Builder
    • 객체의 생성 방법과 조립(구성) 방법의 분리에 중점
    • 아키텍처와 객체 지향에 중점을 둠 (이펙티브 자바보다 오래 전 출판됨)

댓글