정적 팩토리 메서드와 생성자에 똑같은 제약이 하나 있다. 그것은 바로 매개변수가 많을 때 적절히 대응하기 어렵다는 점이다.
만약에, 식품 포장의 영양정보를 표현하는 클래스를 생각해보자. 영양정보는 1회 내용량, 총 n회 제공량, 1회 제공량당 칼로리 같은 필수 항목 몇 개와 총지방, 탄수화물, 나트륨 등 수많은 선택 항목으로 이뤄진다. 이때 대다수 값이 0 이다.
그래서, 값이 있는 항목에 한해 객체 생성시 넣을 수 있도록 생성자를 생성하려하면 파라미터가 끝도 없이 길어질 수 있고, 또한 파라미터에 넣는 값의 순서가 바뀌어도 오류가 나지 않아 어플리케이션이 비정상 적으로 작동될 수 있다. 더 큰 문제는 비정상 작동은 큰 문제가 된 이후에 발견 될 것이다.
결론적으로 생성자의 매개 변수 조합이 다양하고 매개 변수 수 자체가 많을 수록 클라이언트(클래스를 호출해서 사용하는 클래스) 코드를 작성하거나 읽기 어려워 질 것이다.
이러한 점을 보완하고자 점층적 생성자 패턴, 자바빈즈 패턴이 나오긴 했지만 둘 모두 비효율적이였다.
두 패턴을 설명하기 위해 아래와 같은 클래스가 있다고 가정하겠다.
```
public class Niniz{
String name;
int age;
String job;
String address;
}
```
**점층적 생성자 패턴**
클래스 내 변수들을 하나씩 늘려가며 생성자를 만드는 패턴. 주요 변수가 많아지면 수평으로 무궁무진하게 길어질 수 있다.
`public Niniz(String name){};`
`public Niniz(String name, int age){};`
`public Niniz(String name, int age, String job){};`
`public Niniz(String name, int age, String job, String address){};`
**자바 빈즈 패턴**
객체 생성 후 주요 변수를 모두 set 해주는 패턴. 변수가 많은 수록 더 수직으로 더 길어 질 수 있다.
set이 끝나기 전까지는 객체가 불완전한 상태로 놓이게 되며, 그 과정에서 파라미터가 유효한지도 체크 할 수 없어 일관성 유지도 힘들다. 이러한 단점은 불변 클래스(객체 생성 후 변경 되지 않는 클래스)를 생성할 수 없게한다.
```
Niniz niniz= new Niniz();
niniz.setName("jordy");
niniz.setAge(2000);
niniz.setJob("Kakao Intern");
niniz.setAddress(null);
```
위 두 가지 패턴의 장점을 모두 취한 빌더 패턴이 있다.
- 클라이언트(빌더 패턴이 적용된 클래스를 사용하는 클래스)는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자(혹은 정적 팩터리)를 호출해 빌더 객체를 얻는다.
- 그런 다음 빌더 객체가 제공하는 일종의 세터 메서드들로 원하는 매개변수들을 설정한다.
- 마지막으로 매개변수가 없는 build매서드를 호출해 드디어 우리에게 필요한 (보통은 불변인) 객체를 얻는다.
- 빌더는 생성할 클래스 안에 정적 멤버 클래스 (public static class) 로 만들어두는게 보통이다.
아래 코드를 보자.
```
// 영양성분 빌더 패턴 클래스
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 setvings){
this.servingSize = servingSize;
this.servings = servingSize;
}
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);
}
}
public NutritionFacts(Builder builder) {
servingSize = builder.servingSize;
servings = builder.servings;
calories = builder.calories;
fat = builder.fat;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
@Override
public String toString() {
return "NutritionFacts{" +
"servingSize=" + servingSize +
", servings=" + servings +
", calories=" + calories +
", fat=" + fat +
", sodium=" + sodium +
", carbohydrate=" + carbohydrate +
'}';
}
}
/* ------------------------------------------------------------------------------------*/
// Main 메소드
// 영양성분 클래스로 객체를 빌드 한 후 콘솔 확인.
public class Item2_Main {
public static void main(String[] args) {
NutritionFacts cocacola = new NutritionFacts.Builder(240,8)
.calories(100).sodium(35).sodium(27).build();
System.out.println(cocacola);
}
}
/* [console] */
NutritionFacts{servingSize=240, servings=240, calories=100, fat=0, sodium=27, carbohydrate=0}
```
Main 메소드의 클라이언트 코드는 쓰기 쉽고 읽기가 쉽다. 빌더 패턴은 (파이썬과 스칼라에 있는) 명명된 선택적 매개변수를 흉내낸 것이다.
위 코드에 추가적으로 유효성 검사 코드를 가미하면 **완벽** 하다고 한다. 유효성 검사 코드는 빌더의 생성자와 메서드에서 입력 매개변수를 검사하고, build 메서드가 호출하는 생성자에서 여러 매개변수에 걸친 **불변식(invariant)**를 검사하자. 공격에 대비해 불변식을 보장하려면 빌더로부터 매개변수를 복사한 후 해당 객체 필드들도 검사해야 한다.
> **불변 immutable**
> 어떠한 변경도 허용하지 않는 다는 뜻
> ex)String
> **불변식 invariant**
> 프로그램이 실행되는 동안, 혹은 정해진 기간 동안 반드시 만족해야 하는 조건을 말한다.
빌더 패턴은 계층적으로 설계된 클래스와 함꼐 쓰기가 좋다.
'Book' 카테고리의 다른 글
[Effective Java] Item 6. 불필요한 객체 생성을 피하라 (0) | 2021.10.06 |
---|---|
[Effective Java] Item 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라 (0) | 2021.10.06 |
[Effective Java] Item 1. 생성자 대신 정적 팩토리 메서드를 고려하라 (0) | 2021.10.06 |
[카프카, 데이터 플랫폼의 최강자] 6장. 카프카 운영 가이드 (0) | 2021.09.29 |
[카프카, 데이터 플랫폼의 최강자] 5장. 카프카 컨슈머 (0) | 2021.09.29 |