Categories
자바

빌더(builder)는 언제 사용할까

Static 팩토리 메소드와 생성자 사용에는 공통적으로 제약이 있습니다. 매개변수가 많아지면 사용이 어려워지고 빠르게 처리하지 못합니다. 포장 식품에 붙은 영양 정보 라벨을 나타내는 클래스의 경우를 예를 들어보자. 이 라벨에는 식품의 양, 개수, 칼로리와 같은 몇 개의 필수 필드가 있습니다. 그리고 지방, 포화 지방, 트랜스 지방, 콜레스테롤, 나트륨 등 20개 이상의 선택 필드가 있습니다. 대부분의 식품은 선택 필드 중 몇 개에만 값을 갖습니다. 이런 클래스의 경우에 생성자나 static 팩토리 메소드를 어떻게 만들어야 할까? 이런 경우 많은 프로그래머는 telescoping constructor 패턴을 사용했습니다. 즉, 필수 매개변수들만 갖는 생성자, 필수 매개변수들과 선택 매개변수 하나를 갖는 생성자, 필수 매개변수들과 선택 매개변수 두 개를 갖는 생성자 등의 형태로 모든 선택 매개변수를 생성자가 가질 수 있도록 여러 개의 생성 자를 겹겹이 만드는 것입니다. 어떤 형태인지 아래 코드를 살펴보겠습니다.

public class NutritionFacts {
private final int servingSize; private final int servings; | private final int calories; private final int fat; private final int sodium; private final int carbohydrate;
public NutritionFacts(int serving Size, int servings) {
this(servingSize, servings, 0);
public NutritionFacts(int servingSize, int servings,
int calories) { this(servingSize, servings, calories, 0);
public NutritionFacts (int serving Size, int servings,
int calories, int fat) { this(serving Size, servings, calories, fat, 0);
public NutritionFacts(int serving Size, int servings,
int calories, int fat, int sodium) { this(servingSize, servings, calories, fat, sodium, 0);
public NutritionFacts (int serving Size, int servings,
int calories, int fat, int sodium, int carbohydrate) { this.serving Size = servingSize; this.servings = servings; this.calories = calories; this. fat = fat; this.sodium = sodium; this.carbohydrate = carbohydrate;

클래스의 인스턴스를 생성할 때 모든 매개변수를 포함하는 생성자를 사용한다면 다음과 같습니다. NutritionFacts cocaCola =new NutritionFacts (240, 8, 100, 0, 35, 27); 일반적으로 이런 식의 생성자 호출은 우리가 원하지 않는 매개변수에도 초기값을 주어야 합니다. 여기서는 fat의 값으로 0을 주었습니다. 이처럼 여섯 개의 매개변수만 사용한다면 그나마 그리 나쁜 것 같지 않습니다. 그러나 매개변수의 수가 증가하면 무척 번거로워집니다. 텔리스코핑 생성자 패턴은 그런대로 쓸만하지만, 매개변수가 많을 때는 클라이언트 코드 작성이 힘들고 코드의 가독성도 떨어집니다. 코드를 파악하는 사람의 입장에서는 그런 모든 값들이 어떤 의미 를 갖는지 의아한 상태에서 매개변수의 개수를 주의 깊게 세어봐야 합니다. 또한 동일한 타입의 매개 변수들이 길게 연속되어 찾기 어려운 결함을 발생시킬 수 있습니다. 만일 클라이언트 코드에서 실수로 그런 매개변수 두 개를 바꾸어 사용하면, 컴파일 에러는 생기지 않겠지만 프로그램의 실행이 엉뚱하 게 될 것입니다. 매개변수가 많은 생성자의 두 번째 대안으로 자바빈즈(JavaBeans) 패턴이 있습니다. 이 패턴에서는 매 개변수가 없는 생성자를 호출해서 객체를 생성한 후 세터(setter) 메소드를 호출해서 각각의 필수 필드와 선택 필드 값을 지정하는 방식입니다. 하지만 자바빈즈 패턴은 심각한 단점을 갖고 있습니다. 여러 번의 메소드 호출로 나누어져 인스턴스가 생성되므로, 생성 과정을 거치는 동안 자바빈(JavaBean) 객체가 일관된 상태를 유지하지 못할 수 있기때문입니다. 생성자 매개변수의 유효성을 검사하여 일관성을 유지하도록 하는 옵션조차도 클래스에 없기 때문입니다. 또한 일관성 없는 상태의 객체를 사용하려 한다면 결함을 찾기 어려운 문제를 야기시킬 수 있습니다. 이와 연관해서 자바빈즈 패턴은 불변(immutable) 클래스를 만들 수 있는 가능성을 배제하므로, 스레드(thread)에서 안전성을 유지하려면 프로그래머의 추가적인 노력이 필요하다는 단점이 있습니다. 객체 생성이 완전하게 끝났을 때 그 객체를 “동결(freezing)”하고 완전하게 되기 전까지는 사용할 수 없도록 함으로써 그런 단점을 줄일 수 있습니다. 그러나 이런 방법은 적용이 어려워서 실제로는 거의 사용되지 않으며, 더욱이 런타임 에러를 유발할 수도 있습니다. 동결시키는 메소드를 객체 사용 전에 프 로그래머가 호출했는지 컴파일러가 확인할 수 없기 때문입니다. 다행스럽게도 텔리스코핑 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 결합한 세 번째 방법이 있습니다. 그것은 빌더(Builder) 패턴의 형태로써, 원하는 객체를 바로 생성하는 대신 클라이언트는 모든 필수 매개변수를 갖는 생성자(또는 static 팩토리 메소드)를 호출하여 빌더 객체를 얻습니다. 그 다음에 빌더 객체의 세터 메소드를 호출하여 필요한 선택 매개변수들의 값을 설정합니다. 마지막으로, 클라이언트는 매개변수가 없는 build 메소드를 호출하여 불변 객체를 생성합니다. (불변 객체는 생성 후에 상태가 변하지 않습니다. 멤버 변수의 값을 변경하는 세터 메소드를 갖지 않기 때문입니다. String이 대표적인 예입니다.) 빌더는 자신이 생성하는 객체의 클래스에 포함된 static 멤버 클래스입니다.