앞서 소개한 정적 팩터리와 생성자에는 똑같은 제약이 하나 있다.
선택적 매개변수가 많을 때 적절히 대응하기가 힘들다는 점이다.
아래와 같은 클래스가 있다고하자.
필드의 개수가 매우많다.
public class ManyFieldClass {
private int a;
private int b;
private int c;
private int d;
private int e;
private int f;
private int g;
private int h;
private int i;
private int j;
private int k;
private int l;
private int m;
private int n;
private int o;
private int p;
private int q;
private int r;
private int s;
private int t;
private int u;
private int v;
private int w;
private int x;
private int y;
private int z;
}
이런경우에 해당 클래스를 사용하는 모든곳에서 모든 필드를 사용하지 않을것이다.
예를들어 A라는 클래스에서 ManyFieldClass를 사용하는데 a,b,c 라는 필드만 필요하고
B라는 클래스에서 ManyFieldClass를 사용하는데 d,e,f,g 라는 필드만 필요하다.
이럴때 생성자를 만든다면 아래와 같이 작성할것이다.
public ManyFieldClass(int a,int b,int c){
this.a = a;
this.b = b;
this.c = c;
}
public ManyFieldClass(int d,int e,int f, int g){
this.d = d;
this.e = e;
this.f = f;
this.g = g;
}
이런식으로 생성자가 1~2개 심지어 4~5개까지는 필요에따라 선택해서 쓸 수는 있을것이다.
하지만
1. a,b,c 만 필요할때
2. a,b 만 필요할때
3. a,b,f 만 필요할때
4. a,b,c,d가 필요할때
5. b,c 만 필요할때
등등 무수한 경우가 생길경우 생성자나 정적 팩터리 메서드를 이용한다면 어떤 생성자가 있는지 찾기조차 힘들어 질것이다.
a,b,c 만 필요할 때 d,e,f만 필요할때는 int 타입의 매개변수가 3개가 필요하다.
이런경우에는 두개의 생성자를 모두 만드는것조차 불가능할것이다.
매개변수의 타입이 동일하다면 JAVA는 이게 a,b,c 를 초기화하는것인지 d,e,f 를 초기화하는것인지 알지 못하기때문이다.
요약하자면 생성자 패턴을 쓸 수는 있지만 매개변수의 개수가 많아지면 가독성이 심각하게 떨어진다는 치명적인 단점이 있다.
이런 단점을 보완하기 위해 자바빈즈 패턴이 나왔다.
자바 빈즈 패턴에서도 단점이 존재한다.
이 게시글은 자바 빈즈패턴을 알아보는 시간이 아니므로 자바빈즈패턴에 대한 설명은 생략하겠다.
생성자 패턴의 장점과 자바 빈즈패턴의 장점을 합친것이 빌더패턴이다.
빌더의 생성은 아래와 같다.
package item2;
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;
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.servings = servings;
this.servingSize = 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);
}
}
}
import item2.NutritionFacts;
public final class main {
public static void main(String[] args) {
NutritionFacts nutritionFacts = new NutritionFacts.Builder(0,0)
.sodium(100)
.build();
System.out.println(nutritionFacts.getSodium());
}
}
빌더 패턴은 상당히 유연하다.
위의 예제에서 볼 수 있듯 사용하고자 하는 필드만 초기화가 가능하다.
예를들어 생성자 패턴에서는 a부터 z까지 필드가 있는 클래스에서 a만 초기화를 하려면 int형 변수 1개를 가진 생성자가 필요하다. a,d 를 초기화하려면 int형 변수 2개를 가진 생성자가 필요하다.
하지만 빌더패턴을 이용한다면 생성자를 따로 안만들고 위에서 보듯 원하는 변수만 초기화가 가능하다.
이는 클래스가 가지는 필드가 많아질때, 혹은 상황별로 필요한 매개변수가 달라 생성자가 많이 필요하게 될때 굉장히 편리해진다.
실무에서 대부분의 클래스 (100%라고 해도 과언이 아닐정도로)는 필드가 추가된다.
필드를 추가하게 되면 생성자를 그에맞게 다시 생성해야한다.
빌더패턴을 이용한다면 그런수고를 덜 수 있다.
추가한 필드에 대해서 빌더만 추가해주면된다.
빌더 패턴의 가독성은 정말좋으며 사용하기 편리하다.
빌더패턴을 이용한다면 사용하고자 하는 필드가 무엇인지, 초기화하는값이 무엇인지만 명시해주면된다.
생성자를 이용한다면 사용하고자 하는 필드를 초기화할 수 있는 생성자가 있는지 있다면 어떤 생성자인지부터 파악해야한다. 하지만 빌더를 이용한다면 프로그래머가 사용하고자 하는 필드만 빌더를 이용해서 초기화해주면 된다.
사용하기 굉장히 편리하며 추후 유지보수할때도 명시적으로 어떤 필드를 초기화하는지 눈에 보이므로 유지보수성이 좋다. (가독성이 좋으므로)
빌더패턴의 단점으로는 객체를 생성하려면 그에 맞는 빌더를 생성해야된다는 단점이있다.
빌더의 생성 비용이 그렇게 크지는 않지만 성능에 굉장히 민감한 상황일때는 문제가 될수 있다. (대부분의 상황에서는 문제가 되지않는다.)
만들고자 하는 시스템이 성능에 굉장히 민감하다면 테스트를 진행한 후에 결정하도록 하는것이 좋다.
책에서는 시스템을 만들때 처음부터 빌더를 사용하는것을 추천하고 있다.
나도 빌더를 쓴 후로는 대부분의 상황에서 빌더패턴을 적용한다.
참고서적 : 이펙티브 자바 3판
'개발 > JAVA' 카테고리의 다른 글
[effective java 3/E] equals는 일반 규약을 지켜 재정의하라. (0) | 2020.07.05 |
---|---|
[effective java 3/E] 자원을 직접 명시하지말고 의존 객체 주입을 사용하라 (2) | 2020.06.28 |
[effective java 3/E] private 생성자나 열거타입으로 싱글턴임을 보증하라 (0) | 2020.06.26 |
[effective java 3/E] 생성자 대신 정적 팩터리 메서드를 고려해보자 (0) | 2020.06.21 |
이 클래스는 아무것도 상속받지 않을까요 ? (0) | 2020.04.19 |
댓글