728x90
정적 팩터리와 생성자 방식의 문제점
정적 팩터리와 생성자 방식으로 인스턴스를 생성하는 것은 공통적인 제약이 있다.
→ 선택적 매개변수가 많을 경우 적절히 대응하기 어렵다는 것.
public class NutiritionFacts {
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 NutiritionFacts(int servingSize, int servings) {
// servingSize 와 servings 값을 받는 생성자
public NutiritionFacts(int servingSize, int servings, int calories, int fat){
// servingSize, servings, calories, fat 값을 받는 생성자
}
...
}
- 위 코드에는 6개의 필드가 정의되어 있다. 하지만, 모든 필드값들이 필요한 것이 아니라 몇몇 개의 필드만 필요로 하는 인스턴스를 생성할 때, 그 매개변수만 입력으로 받는 생성자를 또 생성해야 하는 문제가 있다
- 7개의 필드를 가진 클래스에도 수많은 생성자 조합이 가능한데, 만약 필드가 10개, 20개가 넘어간다면 일일이 선택적 매개변수를 가지는 생성자를 만들기 어렵다.
자바빈즈 패턴 (JavaBeans pattern)
선택 매개변수가 많을 경우 활용할 수 있는 대안 중 하나다.
자바빈즈 패턴이란 매개변수가 없는 생성자로 객체를 만든 후, setter 메서드들을 호출해 원하는 매개변수의 값을 설정하는 방식이다.
public class NutritionFacts {
private int servingSize;
private int servings;
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
public NutritionFacts() { }
public void setServingSize(int val) { servingSize = val; }
public void setServings(int val) { servings = val; }
public void setCalories(int val) { calories = val; }
public void setFat(int val) { fat = val; }
public void setSodium(int val) { sodium = val; }
public void setCarbohydrate(int val) { carbohydrate = val; }
}
자바빈즈 패턴에서의 심각한 단점
- 객체 하나를 만들기 위해 메서드를 여러 개 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태에 놓이게 된다.
- 이 때문에 클래스를 불변으로 만들 수 없으며, 스레드 안전성을 얻으려면 프로그래머가 추가 작업을 해줘야만 한다.
→ 단점을 해결하기 위해 생성이 끝난 객체를 수동으로 freezing 하고, 얼리기 전에는 사용할 수 없도록 하기도 한다. 하지만 이 방법은 다루기 어려워 실전에서 쓰이지 않는다.
빌더 패턴
점층적 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 겸비한 것이 바로 빌더 패턴이다.
빌더 패턴은 클라이언트가 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자(혹은 정적 팩터리)를 호출해 빌더 객체를 얻는다. 그런 다음 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 선택 매개변수들을 설정한다. 마지막으로 매개변수가 없는 build 메서드를 호출해 우리에게 필요한 객체를 얻는다.
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 static class Builder {
private int servingSize;
private int servings;
private int calories;
private int fat;
private int sodium;
private int carbohydrate;
public Builder() {}
public Builder servingSize(int val) { servingSize = val; return this; }
public Builder servings(int val) { servings = val; return this; }
public Builder calories(int val) { calories = val; return this; }
public Builder fat(int val) { fat = val; return this; }
public Builder sodium(int val) { sodium = val; return this; }
public Builder carbohydrate(int val) { carbohydrate = val; return this; }
public NutritionFacts build(){
return new NutritionFacts(this);
}
}
private NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
빌더 패턴의 단점
- 객체를 만들려면 그에 앞서 빌더부터 만들어야 한다. 빌더 생성 비용이 크지는 않지만 성능에 민감한 상황에서는 문제가 될 수 있음.
- 코드가 장황해서 매개변수가 4개 이상은 되어야 값어치를 한다.
→ 요즘은 lombok 라이브러리가 이를 쉽게 지원해준다.
Summary
생성자나 정적 팩터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는 것이 더 낫다. 매개변수 중 다수가 필수가 아니거나 같은 타입이면 특히 더 그렇다. 빌더는 점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈보다 훨씬 안전하다.
728x90
'북스터디 > 이펙티브 자바' 카테고리의 다른 글
Item 01. 생성자 대신 정적 팩터리 메서드를 고려하라 - 이펙티브 자바 (0) | 2023.02.17 |
---|