요즘 주말마다 디자인 패턴 스터디를 진행하고 있다. 1일 1 블로그를 다짐한 이 시점에 가장 최근에 배운 패턴이기에 정리해보기로 마음 먹었다. (제발 지킬 수 있길...)
빌더 패턴은 GOF의 디자인 패턴을 따르냐 이펙티브 자바 책을 따르냐에 따라 구조가 다르다. 이 둘을 구분하기 위해 이펙티브 자바에서 소개하는 빌더 패턴을 심플 빌더 패턴이라 한다. GOF에서 정의하는 빌더 패턴은 디렉터 빌더 패턴이라고 부른다.

일반적으로 사람들이 빌더 패턴이라고 칭하는 패턴이 바로 이 심플 빌더 패턴이다. 참고한 블로그에선 해당 패턴의 소개를 햄버거에 비유하고 있었는데, 덕분에 서브웨이를 떠올리며 쉽게 이해할 수 있었다.
종종 내 한 끼를 책임지는 서브웨이는 고객이 원하는 샌드위치를 구체적으로 커스터마이징할 수 있다는 장점이 있다. 원하는 빵, 치즈, 야채, 토핑 등을 구체적으로 고르고 빼거나 추가할 수 있는 이 서브웨이를 코드로 구현해본다면 빌더 패턴이 가장 적합하지 않을까?
점층적 생성자 패턴은 생성자 오버로딩을 활용하여 인스턴스를 생성하는 방식이다. 서브웨이를 예로 들자면, 빵은 절대 뺄 수 없다. 하지만, 원하지 않는 야채들은 뺄 수 있다. 빵처럼 꼭 들어가야 하는 대상(변수)와 토마토처럼 필수는 아닌 대상(변수)를 조합하여 다음과 같이 다양한 생성자를 만들 수 있다.
class Subway {
// 필수로 들어가야 하는 대상
private int bread;
// 선택적으로 들어갈 수 있는 대상
private int tomato;
private int olive;
private int egg;
public Subway(int bread, int tomato, int olive, int egg) {
this.bread = bread;
this.tomato = tomato;
this.olive = olive;
this.egg = egg;
}
public Subway(int bread, int olive, int egg) {
this.bread = bread;
this.olive = olive;
this.egg = egg;
}
public Subway(int bread, int egg) {
this.bread = bread;
this.egg = egg;
}
}
해당 방식의 문제점은
첫 번째 생성자를 통해 실제 Subway 인스턴스를 생성하려고 한다면 몇 번째 인자가 olive를 나타내었는지에 혼동이 오기 쉽다. 요즘 IDE 성능이 좋아서 마우스를 가져다대면 n번째 인자가 어떤 인자인지 알려주지만 여전히 가독성이 떨어진다는 단점이 있다.
만약 빵만 존재하는 Subway를 생성하려고 하면 가장 인자 수가 적은 세 번째 생성자를 사용해도 egg에 해당하는 인자를 억지로 0을 넣어주어야 하는 단점이 있다. 즉, 원하지 않는 필드를 생략할 수 없다.
Subway subway = new Subway(2, 0); // 이처럼 불필요한 인자를 굳이 전달하게 만드는 단점이 있다.
자바 빈즈 패턴은 매개변수가 없는 생성자를 통해 인스턴스를 생성한 후 Setter 메서드를 통해 원하는 필드를 설정한다.
class Subway {
// 필수로 들어가야 하는 대상
private int bread;
// 선택적으로 들어갈 수 있는 대상
private int tomato;
private int olive;
private int egg;
public Subway() {}
public void setBread(int bread) {
this.bread = bread;
}
public void setTomato(int tomato) {
this.tomato = tomato;
}
public void setOlive(int olive) {
this.olive = olive;
}
public void setEgg(int egg) {
this.egg = egg;
}
}
자바 빈즈 패턴에서는 setter를 public으로 설정해야 한다. 왜냐하면,
Subway subway = new Subway();
subway.setBread(2);
subway.setTomato(1);
subway.setEgg(1);
다음과 같이 subway 인스턴스를 생성한 곳에서 setter를 통해 Subway의 필드를 마저 지정해주어야 하기 때문이다. 이 방식은 원하는 필드만 setter를 통해 설정하면 되기 때문에 앞서 언급한 점층적 생성자 패턴의 단점을 완벽히 극복하였지만, 일관성 / 불변성 문제가 나타난다.
일관성 문제 : 객체를 생성하는 곳과 값을 설정하는 부분이 떨어져 있게 되면서 필수적으로 들어가야 하는 필드 값을 설정하지 않고 지나칠 수 있다. Subway 예시의 경우 빵을 필수적으로 넣어줘야 하지만, 자바 빈즈 패턴을 통해 생성하던 중 빵을 추가하지 않고 지나칠 위험이 있고 이는 일관성을 무너뜨릴 수 있다.
불변성 문제 : 기존 코드들이 생성자를 통해 필드를 설정했던 것과는 달리, setter를 통해 필드를 설정할 수 있게 되면서 생성과는 다른 시점에 필드를 설정할 수 있게 되었다. 반면 이는 단점으로도 작용을 할 수 있는데, 오히려 생성된 이후로 값이 변하지 않았음을 보장하는 불변성을 지키기 힘들어진다.
빌더 패턴은 복잡한 객체의 생성 과정과 표현 방법을 분리하는 것을 목적으로 한다. 빌더 패턴이 앞선 문제점들을 해결하기 위해선 Builder라는 별도의 클래스를 필요로 한다.
class SubwayBuilder {
private int bread;
private int tomato;
private int olive;
private int egg;
public SubwayBuilder bread(int bread) {
this.bread = bread;
return this;
}
public SubwayBuilder tomato(int tomato) {
this.tomato = tomato;
return this;
}
public SubwayBuilder olive(int olive) {
this.olive = olive;
return this;
}
public SubwayBuilder egg(int egg) {
this.egg = egg;
return this;
}
public Subway build() {
return new Subway(bread, tomato, olive, egg);
}
}
빌더 클래스에서 bread, tomato, olive, egg 함수는 자바 빈즈에서 setter 함수와 같은 역할을 한다. 차이점은 빌더 패턴에서 setter는 this(SubwayBuilder 객체)를 return한다는 점이다. 덕분에 인스턴스를 생성할 때 마치 fetch 함수를 쓸 때와 같이 메서드 체이닝을 통해 생성할 수 있게 된다.
Subway subway = new SubwayBulider()
.bread(2)
.tomato(1)
.egg(1)
.build();
이펙티브 자바의 심플 빌더 패턴이 생성자가 많을 때 사용하는 패턴이라면, 디렉터 빌더 패턴은 객체의 생성과 조립을 분리하는 패턴이다. 디렉터 빌더 패턴에는 디렉터라는 추가적인 클래스가 필요하다.

디렉터 클래스는 복잡한 조립 과정을 클라이언트가 알 필요가 없게 처리한다. 디렉터 클래스에서 조립하기 위해 필요한 부품은 빌더 클래스에서 만든다.
1일 1블로그 생각보다 쉽진 않구나... 하지만 포기하진 않을 거다.
그리고 이펙티브 자바랑 GoF 패턴은 알아서 원만한 합의를 하길 바란다.