Builder 패턴

wjd15sheep·2024년 6월 27일
1

CS

목록 보기
1/9
post-thumbnail

Builder 패턴 의도

빌더는 복잡한 객체들의 생성과 표현 방법을 분리하여 다양한 인스턴스를 만드는 생성 패턴이다. 빌더 패턴을 사용하면 같은 제작 코드를 사용하여 객체의 다양한 유형들과 표현을 제작할 수 있다.

디자인 패턴은 3 가지 종류가 있다.
1. 생성 패턴 : 기존 코드의 유연성과 재사용을 증가시키는 다양한 객체 생성 메커니즘들을 제공
2. 구조 패턴 : 객체들과 클래스들을 구조를 유연하고 효율적으로 유지하면서 더 큰 구조로 조립하는 방법
3. 행위 패턴 : 알고리즘들 및 객체 간의 책임 할당과 관련이 있습니다.

빌더 패턴 사용 이유

  • 점층적 생성자 패턴
    -점층적 생성자 패턴(Telescoping Constructor Pattern)은 필수 매개변수와 함계 선택 매개변수를 0개, 1개, 2개 ... 받는 형태로, 매개변수를 입력 받아 인스턴스를 생성하던 생성자 오버로딩하는 방식이다. 이러한 방식은 인스턴스 필드들이 많으면 인자의 수가 늘어나 가독성이 떨어진다. 또한 매개변수 특성상 순서를 따라야 하므로 중간에 생략이 불가능하다. 그렇다보니 매개변수 타입이 다양할 수록 생성자 메서드 수가 기하급수적으로 늘어나 가독성이나 유지보수 측면에서 좋지 않다.

예시 코드 (Inpa님 참고)

    class Hamburger {
    // 필수 매개변수
    private int bun;
    private int patty;

    // 선택 매개변수
    private int cheese;
    private int lettuce;
    private int tomato;
    private int bacon;

    public Hamburger(int bun, int patty, int cheese, int lettuce, int tomato, int bacon) {
        this.bun = bun;
        this.patty = patty;
        this.cheese = cheese;
        this.lettuce = lettuce;
        this.tomato = tomato;
        this.bacon = bacon;
    }

    public Hamburger(int bun, int patty, int cheese, int lettuce, int tomato) {
        this.bun = bun;
        this.patty = patty;
        this.cheese = cheese;
        this.lettuce = lettuce;
        this.tomato = tomato;
    }
    

    public Hamburger(int bun, int patty, int cheese, int lettuce) {
        this.bun = bun;
        this.patty = patty;
        this.cheese = cheese;
        this.lettuce = lettuce;
    }

    public Hamburger(int bun, int patty, int cheese) {
        this.bun = bun;
        this.patty = patty;
        this.cheese = cheese;
    }

    ...
}


public static void main(String[] args) {
    // 모든 재료가 있는 햄버거
    Hamburger hamburger1 = new Hamburger(2, 1, 2, 4, 6, 8);

    // 빵과 패티 치즈만 있는 햄버거
    Hamburger hamburger2 = new Hamburger(2, 1, 1);

    // 빵과 패티 베이컨만 있는 햄버거
    Hamburger hamburger3 = new Hamburger(2, 0, 0, 0, 0, 6);
}
	

예시 코드는 햄버거를 예를 들어서 작성이 되었다. 모든 재료가 있는 햄버거에 대한 생성자, 빵 패티 치즈만 있는 햄버거 생성자, 빵과 패티 베이컨만 있는 햄버거 생성자... 등 조합에 따라 많은 생성자 코드가 필요할 것이다. 그러면 몇번째 줄에 무엇이 들어있는지 알아보기가 어려워지며 유지보수가 힘들어 진다.

  • 자바 빈(Java Beans) 패턴
    • 점층적 생성자 패턴의 문제를 해결하기 위해 고안된 Setter 메서드를 사용한 자바 빈(Bean) 패턴이다. 매개변수가 없는 생성자로 객체 생성후 Setter 메소드를 이용해 클래스 필드의 초깃값을 설정하는 방식이다.
  public static void main(String[] args) {
    // 모든 재료가 있는 햄버거
    Hamburger hamburger1 = new Hamburger();
    hamburger1.setBun(2);
    hamburger1.setPatty(1);
    hamburger1.setCheese(2);
    hamburger1.setLettuce(4);
    hamburger1.setTomato(6);
    hamburger1.setBacon(8);

    // 빵과 패티 치즈만 있는 햄버거
    Hamburger hamburger2 = new Hamburger();
    hamburger2.setBun(2);
    hamburger2.setPatty(1);
    hamburger2.setCheese(2);

    // 빵과 패티 베이컨만 있는 햄버거
    Hamburger hamburger3 = new Hamburger();
    hamburger3.setBun(2);
    hamburger2.setPatty(1);
    hamburger3.setBacon(8);
}

기존 생성자 오버로딩에서 나타난 가독성 문제점이 사라지고 선택적인 파라미터에 대해 해당되는 Setter 메서드를 호추함으로써 유연적으로 객체 생성이 가능해진다. 하지만 이러한 방식은 객체 생성 시점에 모든 값들을 주입하지 않아 일관성(consistency) 문제와 불변성(immutable) 문제가 나타난다.

Inpa Dev - 빌더Builder-패턴-끝판왕-정리

빌더(Builder) 패턴

빌더 패턴은 자신의 클래스에서 객체 생성 코드를 추출하여 builder(건축업자들)라는 별도의 객체들로 이동하도록 제안한다. 별도의 Builder 클래스를 만들어 메소드를 통해 step-by-step으로 값을 입력받은 후 최종적으로 build() 메소드로 하나의 인스턴스를 생성하여 리턴하는 패턴이다.

class StudentBuilder {
    private int id;
    private String name;
    private String grade;
    private String phoneNumber;

    public StudentBuilder id(int id) { ... }

    public StudentBuilder name(String name) { ... }

    public StudentBuilder grade(String grade) { ... }

    public StudentBuilder phoneNumber(String phoneNumber) { ... }

    public Student build() {
        return new Student(id, name, grade, phoneNumber); // Student 생성자 호출
    }
}

public static void main(String[] args) {

    Student student = new StudentBuilder()
                .id(2016120091)
                .name("임꺽정")
                .grade("Senior")
                .phoneNumber("010-5555-5555")
                .build();

    System.out.println(student);
}

빌더 패턴을 이용하면 더 이상 생성자 오버로딩 열거를 하지 않아도 되며, 데이터의 순서에 상관없이 객체를 만들어내 생성자 인자 순서를 파악할 필요도 없고 가독성도 좋아진다.

디렉터(관리자)

빌더 단계들에 대한 일련의 호출을 디렉터(관리자)라는 별도의 클래스로 추출할 수 있다. 디렉터 클래스는 제작 단계들을 실행하는 순서를 정의하는 반면 빌더는 이러한 단계들에 대한 구현을 제공한다.
프로그램에 디렉터 클래스를 포함하는 것은 필수사항은 아니다. 그러나 디렉터 클래스는 다양한 생성 루틴들을 배치하여 프로그램 전체에서 재사용할 수 있는 좋은 장소가 될 수 있다.
또한 디렉터 클래스는 클라이언트 코드에서 제품 생성의 세부 정보를 완전히 숨긴다. 클라이언트는 빌더를 디렉터와 연관시키고 디렉터와 생성을 시행한 후 빌더로 부터 결과를 얻기만 하면 된다.

  1. 빌더
    • 인터페이스는 모든 유형의 빌더들에 공통적인 제품 생성 단계들을 선언
  2. 구상 빌더
    • 생성 단계들의 다양한 구현을 제공 또 구상 빌더들은 공통 인터페이스를 따르지 않는 제품들도 생성할 수 있습니다.
  3. 제품들
    • 결과로 나온 객체들
  4. 디렉터
    • 생성 단계들을 호출하는 순서를 정의하므로 제품들의 특정 설정을 만들고 재사용할 수 있습니다.
  5. 클라이언트
    • 빌더 객체들 중 하나를 디렉터와 연결해야 한다. 일반적으로 위 연결은 디렉터 생성자의 매개변수들을 통해 한번만 수행되며, 그 후 디렉터는 모든 추가 생성에 해당 빌더 객체들을 사용한다. 그러나 클라이언트가 빌더 객체를 디렉터의 프로덕션 메서드에 전달할 때를 위한 대안적 접근 방식이 있다. 이 경우 디렉터와 함께 무언가를 만들 때마다 다른 빌더를 사용할 수 있다.

디렉터 코드 예시

public class Director {

    public void constructSportsCar(Builder builder) {
        builder.setCarType(CarType.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.setCarType(CarType.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.setCarType(CarType.SUV);
        builder.setSeats(4);
        builder.setEngine(new Engine(2.5, 0));
        builder.setTransmission(Transmission.MANUAL);
        builder.setGPSNavigator(new GPSNavigator());
    }
}

... 

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

        // Director gets the concrete builder object from the client
        // (application code). That's because application knows better which
        // builder to use to get a specific product.
        CarBuilder builder = new CarBuilder();
        director.constructSportsCar(builder);

        // The final product is often retrieved from a builder object, since
        // Director is not aware and not dependent on concrete builders and
        // products.
        Car car = builder.getResult();
        System.out.println("Car built:\n" + car.getCarType());


        CarManualBuilder manualBuilder = new CarManualBuilder();

        // Director may know several building recipes.
        director.constructSportsCar(manualBuilder);
        Manual carManual = manualBuilder.getResult();
        System.out.println("\nCar manual built:\n" + carManual.print());
    }

이 코드는 Builder 패턴을 사용하여 자동차와 자동차 설명서를 생성한다. Director 클래스는 자동차 타입에 따라 다양한 자동차를 구성하는 방법을 정의한다. CarBuilder와 CarManualBuilder는 실제 자동차와 자동차 설명서를 만드는 구체적인 빌더들이다.

빌더 패턴 장단점

장점

  • 객체들을 단계별로 생성하거나 생성 단계들을 연기하거나 재귀적으로 단계들을 생행할 수 있다.
  • 제품들의 다양한 표현을 만들 때 같은 생성 코드를 재사용할 수 있다.
  • 단일 책임 원칙. 제품의 비즈니스 로직에서 복잡한 생성 코드를 고립시킬 수 있다.

단점

  • 패턴이 여러 개의 새 클래스들을 생성해야 하므로 코드의 전반적인 복잡성이 증가한다.

다른 패턴과의 관계

  • 빌더는 복잡한 객체들을 단계별로 생성하는 데 중점을 둔다. 추상 팩토리 는 관련된 개체들의 패밀리들을 생성하는 데 중점을 둔다. 추상 팩토리는 제품을 즉시 반환하지만 빌더는 제품을 가져오기 전에 당신이 몇 가지 추가 생성 단계들을 실행할 수 있도록 한다.
  • 복잡한 패턴 트리를 생성할 때 빌더를 사용할 수 있습니다. 빌더의 생성 단계들을 재귀적으로 작동하도록 프로그래맹할 수 있기 때문이다.
  • 빌더브리지와 조합할 수 있다. 디렉터 클래스는 추상화의 역할을 하고 다양한 빌더들은 구현의 역할을 한다.
  • 추상 팩토리들, 빌더들프로토타입들은 모두 싱글톤으로 구현할 수 있다.

Java 코드 예시


참고하면 좋은 글


[참고]

profile
성장 위해 노력하는 웹 개발자 주니어

0개의 댓글