생성자를 통한 객체생성은 편리하지만, 인수의 개수가 증가하면 가독성에 문제가 생긴다.
예를들어, 인수가 5개인 객체의 생성은 new MyObject("param1", "param2", "param3", "param4", "param5")
가 된다.
가독성문제에 추가로 생성자가 여러개인 경우에도 문제가 발생한다.
IDE의 도움이 있다면 현재 위치에 입력하는 매개변수가 어떤 값인지 파악할 수 있지만, 결국 원하는 매개변수 조합을 사용하는 생성자를 찾는 것은 쉽지않다.
사용자는 클래스를 직접 방문하여 자신이 사용할 생성자가 있는지 생성자목록을 확인해야한다.
이러한 불편함을 해소하기 위한 디자인패턴이 빌더패턴이다.
빌더 패턴의 핵심개념은 객체의 불변성을 유지하면서 필요한 인수를 선택적으로 작성하는 것이다.
즉, 자신이 입력한 인수는 그대로 반영되고 입력하지 않은 경우에는 기본값이 할당된다.
이를 구현하는데 있어 가장먼저 떠오르는 방법은 기본생성자와 Setter를 이용하는 것이다.
기본생성자를 통해 초기객체를 생성하고, Setter를 통해 원하는 매개변수만 초기화시키는 방식으로 동작한다.
다만, 이 방법은 불완전한 객체가 외부에 노출될 위험이 존재한다.
따라서 중간클래스를 이용하여 객체생성 과정을 정형화시키고, 완전한 객체만 반환하도록 하는 것이 필요하다.
Refactoring Guru에서 소개하는 클래스 구조도에는 중간클래스를 활용한 구현방식이 소개되어있다.
Director
는 클라이언트를 대신하여 객체 생성을 이뤄주는 클래스다.
클라이언트가 Director
에게 동작해야할 Builder
클래스를 제공하면,
제공받은 빌더클래스를 이용하여 정해진 순서대로 객체를 조립하여 최종 객체를 반환한다.
중간클래스를 이용한 빌더패턴의 구현방법 외에도 중첩클래스를 이용한 빌더패턴 구현도 자주 사용된다.
public class Product {
private String field1;
//...
private Product(){}
private Product(Builder builder){
this.field1 = builder.field1;
// ...
}
public static class Builder{
private String field1;
// ...
public Product build(){
return new Product(this);
}
public Builder field1(String field1){
this.field1 = field1;
return this;
}
// ...
}
}
중첩클래스를 활용한 빌더패턴은 위 형태로 구현된다.
빌더패턴을 적용시킬 클래스 내부에 Builder
클래스를 작성하고, 기본생성자를 private
설정하여 외부에 노출되지 않도록 설정한다.
빌더 클래스 내부에는 원본클래스의 필드가 존재하며, 각 필드에 해당하는 메서드를 통해 값을 설정한다.
선택적인 필드가 아닌경우 final
과 빌더 생성자를 통해 호출시점에서 값을 설정할 수 있다.
설정하지 않은 필드는 기본값을 사용한다
이후, build()
를 통해 원본 클래스의 생성자를 호출하여 빌더클래스 필드값이 반영된 객체를 생성한다.
Product build = new Product.Builder()
.field1("field1")
.field2("field2")
.build();
이 방법에서는 메서드 체이닝을 이용해 위 형식으로 원하는 필드값만 입력된 객체생성이 가능하다.
이 외에도 제네릭을 사용하여 계층적으로 설계된 클래스에 빌더패턴을 적용시킬 수도 있다.
최상위 클래스에
Builder<T extends Builder<T>>
형식의 추상 빌더 클래스를 작성하고, 이를 상속하는 클래스에서 세부적인 내용을 구현함으로써 공통적인 필드작성과 세부 필드작성을 구분하여 관리할 수 있다.