
빌더는 복잡한 객체들의 생성과 표현 방법을 분리하여 다양한 인스턴스를 만드는 생성 패턴이다. 빌더 패턴을 사용하면 같은 제작 코드를 사용하여 객체의 다양한 유형들과 표현을 제작할 수 있다.
디자인 패턴은 3 가지 종류가 있다.
1. 생성 패턴 : 기존 코드의 유연성과 재사용을 증가시키는 다양한 객체 생성 메커니즘들을 제공
2. 구조 패턴 : 객체들과 클래스들을 구조를 유연하고 효율적으로 유지하면서 더 큰 구조로 조립하는 방법
3. 행위 패턴 : 알고리즘들 및 객체 간의 책임 할당과 관련이 있습니다.
가독성이 떨어진다. 또한 매개변수 특성상 순서를 따라야 하므로 중간에 생략이 불가능하다. 그렇다보니 매개변수 타입이 다양할 수록 생성자 메서드 수가 기하급수적으로 늘어나 가독성이나 유지보수 측면에서 좋지 않다.예시 코드 (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);
}
예시 코드는 햄버거를 예를 들어서 작성이 되었다. 모든 재료가 있는 햄버거에 대한 생성자, 빵 패티 치즈만 있는 햄버거 생성자, 빵과 패티 베이컨만 있는 햄버거 생성자... 등 조합에 따라 많은 생성자 코드가 필요할 것이다. 그러면 몇번째 줄에 무엇이 들어있는지 알아보기가 어려워지며 유지보수가 힘들어 진다.
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) 문제가 나타난다.
빌더 패턴은 자신의 클래스에서 객체 생성 코드를 추출하여 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);
}
빌더 패턴을 이용하면 더 이상 생성자 오버로딩 열거를 하지 않아도 되며, 데이터의 순서에 상관없이 객체를 만들어내 생성자 인자 순서를 파악할 필요도 없고 가독성도 좋아진다.
빌더 단계들에 대한 일련의 호출을 디렉터(관리자)라는 별도의 클래스로 추출할 수 있다. 디렉터 클래스는 제작 단계들을 실행하는 순서를 정의하는 반면 빌더는 이러한 단계들에 대한 구현을 제공한다.
프로그램에 디렉터 클래스를 포함하는 것은 필수사항은 아니다. 그러나 디렉터 클래스는 다양한 생성 루틴들을 배치하여 프로그램 전체에서 재사용할 수 있는 좋은 장소가 될 수 있다.
또한 디렉터 클래스는 클라이언트 코드에서 제품 생성의 세부 정보를 완전히 숨긴다. 클라이언트는 빌더를 디렉터와 연관시키고 디렉터와 생성을 시행한 후 빌더로 부터 결과를 얻기만 하면 된다.

디렉터 코드 예시
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는 실제 자동차와 자동차 설명서를 만드는 구체적인 빌더들이다.
추상 팩토리 는 관련된 개체들의 패밀리들을 생성하는 데 중점을 둔다. 추상 팩토리는 제품을 즉시 반환하지만 빌더는 제품을 가져오기 전에 당신이 몇 가지 추가 생성 단계들을 실행할 수 있도록 한다.빌더를 사용할 수 있습니다. 빌더의 생성 단계들을 재귀적으로 작동하도록 프로그래맹할 수 있기 때문이다.빌더를 브리지와 조합할 수 있다. 디렉터 클래스는 추상화의 역할을 하고 다양한 빌더들은 구현의 역할을 한다.추상 팩토리들, 빌더들 및 프로토타입들은 모두 싱글톤으로 구현할 수 있다.참고하면 좋은 글
[참고]