빌더 패턴

생성 조건이 복잡한 객체들을 단계별로 생성할 수 있도록 하는 패턴. 다양한 타입과 표현의 객체를 생성할 수 있다.

 

연습 문제

아래 코드를 빌더 패턴으로 리팩토링 해보세요.

public class HouseA {
    int garage;
    int garden;
    int swimmingPool;
    Integer flower;

    // 점층적 생성자 - 매개변수 조합마다 생성자 필요
    public HouseA(int garage, int garden, int swimmingPool, Integer flower) {
        this.garage = garage;
        this.garden = garden;
        this.swimmingPool = swimmingPool;
        this.flower = flower;
    }

    public HouseA(int garage, int garden, int swimmingPool) {
        this(garage, garden, swimmingPool, null);
    }

    public HouseA(int garage, int garden) {
        this(garage, garden, 0, null);
    }

    public HouseA(int garage) {
        this(garage, 0, 0, null);
    }
}

public class Main {
    public static void main(String[] args) {
        HouseA house = new HouseA(1, 2, 1);
    }
}

 

요구사항

  • Builder 인터페이스: 모든 빌더에 공통인 생성 단계들 선언
  • ConcreteBuilder: 생성 단계들의 구체적 구현 제공
  • Product: 빌더가 생성하는 결과 객체
  • Director: 생성 단계들의 호출 순서를 정의 (선택사항)
Builder (interface)
├── buildGarage()
├── buildGarden()
├── plantFlower()
└── get(): Product

ConcreteBuilder
├── HouseABuilder
└── HouseBBuilder

Product
├── HouseA
└── HouseB

Director (선택사항)
└── make(type): 빌더 단계들을 조합하여 특정 설정의 제품 생성

 

현재 문제점

  1. 가독성 저하 - new HouseA(1, 2, 1) 만 보고 각 값이 뭔지 알 수 없음
  2. 점층적 생성자 - 매개변수 조합마다 생성자를 만들어야 함
  3. 실수 유발 - 매개변수 순서 헷갈리면 버그 발생

 

정답

1. Product 인터페이스와 구현

public interface House {
    void print();
}

public class HouseA implements House {
    public int garage;
    public int garden;
    public int swimmingPool;
    public Integer flower;

    @Override
    public void print() {
        System.out.println("HouseA: " + this);
    }

    @Override
    public String toString() {
        return "HouseA{" +
                "garage=" + garage +
                ", garden=" + garden +
                ", swimmingPool=" + swimmingPool +
                ", flower=" + flower +
                '}';
    }
}

public class HouseB implements House {
    int garage;
    int garden;
    int swimmingPool;
    Integer flower;

    @Override
    public void print() {
        System.out.println("HouseB: " + this);
    }

    @Override
    public String toString() {
        return "HouseB{" +
                "garage=" + garage +
                ", garden=" + garden +
                ", swimmingPool=" + swimmingPool +
                ", flower=" + flower +
                '}';
    }
}

2. Builder 인터페이스

public interface HouseBuilder {
    HouseBuilder buildGarage(int num);
    HouseBuilder buildGarden(int num);
    HouseBuilder plantFlower(int num);
    House get();
}

3. ConcreteBuilder 구현

public class HouseABuilder implements HouseBuilder {
    private HouseA house = new HouseA();

    @Override
    public HouseBuilder buildGarage(int num) {
        this.house.garage = num;
        return this;
    }

    @Override
    public HouseBuilder buildGarden(int num) {
        this.house.garden = num;
        return this;
    }

    @Override
    public HouseBuilder plantFlower(int num) {
        this.house.flower = num;
        return this;
    }

    @Override
    public House get() {
        House result = this.house;
        this.house = new HouseA();  // 다음 빌드를 위해 리셋
        return result;
    }
}

public class HouseBBuilder implements HouseBuilder {
    private HouseB house = new HouseB();

    @Override
    public HouseBuilder buildGarage(int num) {
        this.house.garage = num;
        return this;
    }

    @Override
    public HouseBuilder buildGarden(int num) {
        this.house.garden = num;
        return this;
    }

    @Override
    public HouseBuilder plantFlower(int num) {
        this.house.flower = num;
        return this;
    }

    @Override
    public House get() {
        House result = this.house;
        this.house = new HouseB();
        return result;
    }
}

4. Director (선택사항)

public class Director {
    private HouseBuilder builder;

    public Director(HouseBuilder builder) {
        this.builder = builder;
    }

    public House make(String type) {
        if (type.equals("simple")) {
            return builder.buildGarage(1).get();
        } else {
            return builder
                    .buildGarage(2)
                    .buildGarden(1)
                    .plantFlower(10)
                    .get();
        }
    }
}

5. 클라이언트 코드

public class Main {
    public static void main(String[] args) {
        
        // 방법 1: 빌더 직접 사용 - 메서드 체이닝
        House house = new HouseABuilder()
                .buildGarage(2)
                .buildGarden(1)
                .plantFlower(10)
                .get();
        house.print();

        // 방법 2: Director 사용 - 미리 정의된 설정
        HouseABuilder builder = new HouseABuilder();
        Director director = new Director(builder);
        
        House simpleHouse = director.make("simple");
        simpleHouse.print();
    }
}

 

 


참조 

https://refactoring.guru/ko/design-patterns/builder

+ Recent posts