2019. 3. 7. 13:10ㆍJava/Effective Java
객체를 생성할 때 반드시 필요한 필수 인자와 옵션 인자.
이때, 옵션 인자가 너무 많을 경우 어떤 형태로 객체를 생성하는게 좋을까요?
- 점층적 생성자 패턴 ( telescoping constructor pattern)
- 자바빈 패턴 ( javabean pattern )
- 빌더 패턴 ( Builder pattern )
이 중에 빌더 패턴을 사용하기를 권장합니다!
나머지 두개의 패턴이 어떤 형태인지 보고, 왜 빌더 패턴을 권장하는지 알아보도록 합시다.
1. 점층적 생성자 패턴
필수 인자를 가진 생성자부터 시작해서, 선택적으로 필요한 인자들을 하나씩 추가한 점층적 생성자 패턴
영양 성분표 클래스를 예시로 들어보면
영양 성분표 중 servingSize, servings만 필수 인자이고 나머지는 옵션적으로 들어옵니다.
영양 성분표는 한번 생성되면 변경되지 않으니 각각 final 로 필드값을 지정할합니다. ( 변경 불가능한(immutable) 클래스로 생성 )
그리고 AllArgsConstructor에서 각 필드들을 초기화 하며,
다른 constructor에서는 AllArgsConstructor를 이용하는 형태로 만듭니다. 아래와 같이!
public class NutritionFacts { private final int servingSize; //총 제공량 private final int servings; // 1회 제공량 private final int calories; // 1회 제공량당 칼로리 private final int fat; // 총 지방함량 private final int sodium; // 총 나트륨 함량 private final int carbohydrate; // 총 탄수화물 함량 public NutritionFacts(int servingSize, int servings) { this(servingSize, servings, 0); } public NutritionFacts(int servingSize, int servings, int calories) { this(servingSize, servings, calories, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat) { this(servingSize, servings, calories, fat, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) { this(servingSize, servings, calories, fat, sodium, 0); } public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) { this.servingSize = servingSize; this.servings = servings; this.calories = calories; this.fat = fat; this.sodium = sodium; this.carbohydrate = carbohydrate; } }
사용할 때에는
NutritionFacts nf = new NutritionFacts(100, 300, 0, 10);
이렇게 사용하면 됩니다.
단점
- 설정할 필요없는 인자도 전달해야하는 경우가 있습니다.
- 인자가 너무 많아서 사용하기도 번거롭고 인자를 잘못된 위치에 넣어서 에러를 초래할 수 있습니다.
2. 자바빈 패턴
자바빈 패턴은 우리가 DTO 클래스를 정의 할 때, 자주 이용하고 있는 형태로,
NoArgsConstructor 호출로 객체 생성 후, 필요한 필드값을 setter로 설정합니다.
public class NutritionFacts { private int servingSize; //총 제공량 private int servings; // 1회 제공량 private int calories; // 1회 제공량당 칼로리 private int fat; // 총 지방함량 private int sodium; // 총 나트륨 함량 private int carbohydrate; // 총 탄수화물 함량 public NutritionFacts() { } public void setServingSize(int servingSize) { this.servingSize = servingSize; } public void setServings(int servings) { this.servings = servings; } public void setCalories(int calories) { this.calories = calories; } public void setFat(int fat) { this.fat = fat; } public void setSodium(int sodium) { this.sodium = sodium; } public void setCarbohydrate(int carbohydrate) { this.carbohydrate = carbohydrate; } }
사용할 때는
NutritionFacts nf = new NutritionFacts(); nf.setServingSize(100); nf.setServings(300); nf.setFat(10); System.out.println(nf);
단점
- 1번의 함수 호출로 객체 생성을 끝낼 수 없다.
: 객체의 일관성(consistency)가 일시적으로 깨질 수 있다. - 변경 불가능(immutable) 클래스를 만들 수 없다.
: setter를 통해 나중에 값을 설정하는 형태이기 때문에 final 설정이 불가능 하다.
3. 빌더 패턴
점층적 생성자 패턴의 안전성과 자바빈 패턴의 가독성의 장점을 합친 것이 빌더 패턴입니다.
public class NutritionFacts { private final int servingSize; //총 제공량 private final int servings; // 1회 제공량 private final int calories; // 1회 제공량당 칼로리 private final int fat; // 총 지방함량 private final int sodium; // 총 나트륨 함량 private final int carbohydrate; // 총 탄수화물 함량 private NutritionFacts(Builder builder) { servingSize = builder.servingSize; servings = builder.servings; calories = builder.calories; fat = builder.fat; sodium = builder.sodium; carbohydrate = builder.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 calories) { this.calories = calories; return this; } public Builder fat(int fat) { this.fat = fat; return this; } public Builder sodium(int sodium) { this.sodium = sodium; return this; } public Builder carbohydrate(int carbohydrate) { this.carbohydrate = carbohydrate; return this; } public NutritionFacts build() { return new NutritionFacts(this); } } }
NutritionFacts 클래스 내에 필드들이 모두 final 처리 되어있는 것은 점층적 생성자 패턴과 같으나,
내부에 Builder라는 public static 클래스가 정의되어있는 것 부터 다릅니다.
Builder내의 필드는
필수 인자는 final 처리하고, (불변!) , 옵션 인자는 final처리를 하지 않습니다.
그리고, Builder의 servingSize, servings 는 생성자로 초기화를 시키고 나머지 옵션들은 설정메서드를 통해 설정을 하는데
이때 설정메서드의 리턴값은 Builder 자신이기 때문에 체인처럼 쭉쭉 이어서 쓸 수 있습니다.
바로 아래와 같은 형태로 사용할 수 있습니다.
new NutritionFacts.Builder(100, 300).calories(420).fat(10).sodium(20).carbohhydrate(90)
여기서는 각 설정메서드에 인자값들이 1개씩 들어있지만, (마치 setter 처럼)
필요에 따라 여러개씩 써서 한꺼번에 설정하는 것도 됩니다.
빌더 패턴은 유연합니다.
하나의 빌더 객체로 여러개의 객체를 생성할 수 있고,
설정 메서드를 호출하여 다음에 생성할 객체의 필드값을 바꿀 수도 있습니다.
자세한 사항은 아래의 코드를 보면 알 수 있습니다.
Builder builder = new NutritionFacts.Builder(100, 300); NutritionFacts nf1 = builder.sodium(30).build(); builder.calories(500); NutritionFacts nf2 = builder.build(); builder.fat(20).sodium(10); NutritionFacts nf3 = builder.build(); NutritionFacts nf4 = builder.build(); System.out.println(nf1); System.out.println(nf2); System.out.println(nf3); System.out.println(nf4);
builder.calories(500)을 호출함으로써 그다음에 생성하는 객체인 nf2, nf3, nf4의 칼로리 필드값이 500으로 설정된 것을 알 수 있습니다
출력 결과값>
NutritionFacts [servingSize=100, servings=300, calories=0, fat=0, sodium=30, carbohydrate=0] NutritionFacts [servingSize=100, servings=300, calories=500, fat=0, sodium=30, carbohydrate=0] NutritionFacts [servingSize=100, servings=300, calories=500, fat=20, sodium=10, carbohydrate=0] NutritionFacts [servingSize=100, servings=300, calories=500, fat=20, sodium=10, carbohydrate=0]
'Java > Effective Java' 카테고리의 다른 글
[Effective Java] Item 10~11. equals는 일반 규약을 지켜서 재정의하라 (0) | 2024.03.10 |
---|---|
Item 9. close 처리해야하는 resource는 try-with-resource를 이용하자 (0) | 2019.03.12 |
Item 6. 불필요한 객체 생성을 피하자 (0) | 2019.03.12 |
Item 3~5. private 생성자 또는 열거타입으로 Singleton 보증하라 (0) | 2019.03.10 |
Item 1. constructor 대신 static factory method를 사용할 수 없는지 생각하라. (0) | 2019.03.07 |