선택적 매개변수가 많을 때 사용할 수 있는 패턴으로는 점층적 생성자 패턴, 자바빈즈 패턴, 빌더 패턴이 있다.
점층적 생성자 패턴이란 필요한 매개변수에 따라 생성자 혹은 정적 팩토리 메서드를 여러개 만드는 방식이다.
하지만 매개변수가 많아지면 잘못된 매개변수를 넘기거나 매개변수 순서가 바뀔 수도 있어서 버그를 유발한다
자바빈즈 패턴이란 매개변수가 없는 생성자로 객체를 만든 후 세터메서드를 통해 매개변수의 값을 설정하는 방식이다
객체 하나를 만들기 위해 여러 메서드를 호출해야 하고, 객체가 완전히 생성되기 전까지는 일관성이 무너진 상태이다.
클래스를 불변으로 만들 수 없으며 스레드 안전성을 위해 프로그래머가 추가적으로 작업해줘야 한다.
빌더패턴이란 빌더객체를 생성하고 메서드체이닝을 통해 원하는 매개변수를 설정하고 build 메서드로 객체를 얻는 방식이다.
빌더는 보통 생성할 클래스 안에 정적 멈버 클래스로 구현한다.
다음은 교재에 나온 빌더 구현 예제이다.
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 static class Builder {
private final int servingSize;
private final int servings;
private int calories = 0;
private int fat = 0;
private int sodium = 0;
private int carbohydrate = 0;
public Builder(int servingSize, int servings) {
this.servingSize = servingSize;
this.servings = servings;
}
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;
}
}
빌더패턴은 계층적으로 설계된 클래스와 함께 사용하기 좋다.
아래 예제는 Pizza라는 추상클래스와 Pizza의 구체 하위 클래스인 NyPizza, Calzone 클래스를 구현한 코드이다.
계층적 빌더를 사용하여 하위 클래스의 메서드가 상위클래스의 메서드가 정의한 반환 타입이 아닌, 그 하위 타입을 반환한다.
public abstract class Pizza {
public enum Topping {HAM, MUSHROOM, ONION, PEPPER, SAUSAGE}
final Set<Topping> toppings;
abstract static class Builder<T extends Builder<T>>{
EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class);
public T addTopping(Topping topping){
toppings.add(Objects.requireNonNull(topping));
return self();
}
abstract Pizza build();
protected abstract T self();
}
public Pizza(Builder<?> builder) {
this.toppings = builder.toppings.clone();
}
}
public class NyPizza extends Pizza{
public enum Size{SMALL, MEDIUM, LARGE}
private final Size size;
public static class Builder extends Pizza.Builder<Builder>{
private final Size size;
public Builder(Size size) {
this.size = Objects.requireNonNull(size);
}
@Override
Pizza build() {
return new NyPizza(this);
}
@Override
protected Builder self() {
return this;
}
}
public NyPizza(Builder builder) {
super(builder);
this.size = builder.size;
}
}
public class Calzone extends Pizza{
private final boolean sauceInside;
public static class Builder extends Pizza.Builder<Builder>{
private boolean sauceInside = false;
public Builder sauceInside(){
sauceInside = true;
return this;
}
@Override
Pizza build() {
return new Calzone(this);
}
@Override
protected Builder self() {
return this;
}
}
public Calzone(Builder builder) {
super(builder);
this.sauceInside = builder.sauceInside;
}
}
빌더패턴은 유연하다.
일관성을 유지하면서도 간편하게 객체를 생성할 수 있다.
하지만 성능이 중요한 경우 빌더 생성를 생성하는 과정에서 성능이 저하될 수도 있다
또한 생성자에 비해 필요한 사전준비가 많다.
따라서 빌더 패턴은 매개변수가 4개 이상은 되어야 값어치를 한다.
필요한 매개변수의 개수가 점점 늘어나는 경우 빌더 패턴을 사용하는 것이 좋다.
항상 Lombok을 통해서만 사용했던 builder를 직접 구현하는 방법에 대해 배웠다.
builder를 왜 사용하는지 깨달았고, builder의 유연함에 감탄했다.
앞으로 매개변수가 많은 객체를 생성해야할 때는 builder 사용을 적극적으로 고려해보기로 했다.
[이펙티브 자바] Item 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 | 2장 (1) | 2023.04.05 |
---|---|
[이펙티브 자바] Item 4. 인스턴스화를 막으려거든 private 생성자를 사용하라 | 2장 (0) | 2023.03.29 |
[이펙티브 자바] Item 3. private 생성자나 열거타입으로 싱글턴임을 보증하라 | 2장 (0) | 2023.03.29 |
[이펙티브 자바] Item 1. 생성자 대신 정적 팩토리 메서드를 고려하라 | 2장 (2) | 2023.03.15 |
[이펙티브 자바] 개요, 자바 기본개념 | 1장 (0) | 2023.03.15 |