Item 2. 빌더 패턴

심규환·2022년 1월 6일
0

Effective Java

목록 보기
2/29
post-thumbnail

정적 팩터리와 생성자에는 똑같은 제약이 하나 있다. 바로 선택적 매개변수가 많다면 적절하게 대응하기 어렵다는 것이다.
예를 들어, 식품 포장의 영양정보를 표현하는 클래스를 생각해보자. 영양정보로 A, B, C, D....F.. 대략 20가지가 넘는 항목으로 나눠진다고 보면
대부분의 항목은 0으로 되어 있고 몇 개의 항목만이 값이 들어있다.
그렇다면 생성자를 통해서 객체를 생성할 때, 필요 없는 항목(0 값)들도 매개변수로 다 넣어줘야 한다. 이를 해결하기 위해 3가지 대안이 제시된다. 바로 점층적 생성자 패턴(telescoping constructor pattern), 자바빈즈 패턴(JavaBeans pattern), 빌더 패턴(Builder pattern) 이다.
하나씩 살펴 보자.

점층적 생성자 패턴(Telescoping constructor pattern)

점층적 생성자 패턴이란, 다수의 생성자를 만드는 패턴이다. 예를 들어, 필수 매개변수만 받는 생성자, 필수 매개변수와 선택적 매개변수 1개만 받는 생성자, 2개만 받는 생성자.... 이렇게 점층적으로 늘려가는 방식이다. 다만 이 패턴은 만약 3번째 선택적 매개변수 값을 넣은 객체를 생성하고 싶은데, 두 번째 선택적 매개변수 값이 필요 없어도 값을 넣어줘야 한다.
그리고 매개변수의 수가 늘어나면 값들을 다 넣어줘야 하는데, 이는 벌써부터 골치가 아파진다. 어떤 매개변수가 어느 위치에 넣어야 하는지 확인해야 하고 타입도 신경써야 한다. 만약 같은 타입이 연달아 있다면 실수라도 하면 찾기 어려운 버그가 생성될 수 있다.

점층 생성자 패턴 코드 (확장의 어려움)

	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 NutritionFacts(int servingSize, int servings) {
			this(servingSize, servings, 0);
		}
		
		public NutritionFacts(int servingSize, int servings, int calories) {
			this(servingSize, serving, calories, 0);
		}
		
		public NutritionFacts(int servingSize, int servings, int calories, int fat) {
			this(servingSize, serving, calories, fat, 0);
		}
		
		public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
			this(servingSize, serving, calories, fat sodium, 0);
		}
		
		public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
			....
		}
	}

2. 자바빈즈 패턴(JavaBeans pattern)

자바빈즈 패턴이란, 간단하게 요약하면 매개변수 없는 빈 생성자를 생성하고, setter를 사용해서 값을 채우는 패턴이다.
자바빈즈 패턴은 점층적 생성자 패턴의 단점인 어떤 값을 넣는지 가독성이 힘든 것을 해결하게 된다. 하지만 심각한 단점이 있다. 객체를 하나 만들려고 한다면 여러 개의 메서드를 호출해야 한다. 그리고 완성된 객체가 만들어지기 전까지 일관성(consistency)이 무너진다.
그렇기 때문에 불변으로 만들 수 없다. setter를 사용하게 되면 어디선가 누구에게 아무도 모르게 수정될 수 있기 때문에 권장하지 않는다.

자바빈즈 패턴 (일관성이 무너지고 불변으로 생성하기 어려움)

	public class NutritionFacts {
		// 매개변수 들은 기본값으로 초기화한다.
		private int servingSize = -1; // 필수
		private int servings = -1; // 필수
		private int calories = 0;
		private int fat = 0;
		private int sodium = 0;
		private int carbohydrat =0;
		
		// 빈 생성자
		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 setCarbohydrat(int val) { carbohydrat = val; }
	}

3. 빌더 패턴(Builder pattern)

위의 두가지 패턴의 장점만을 가져온게 바로 빌더 패턴이다. 점층적 생성자 패턴의 안정성과 자바빈즈 패턴의 가독성을 가지고 있다.
일반적인 생성 순서는 필수 매개변수를 넣은 생성자로 객체 생성 -> 일종의 세터 메서드들로 원하는 선택 매개변수 설정 -> 마지막으로 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 final int calories = 0;
			private final int fat = 0; 
			private final int sodium = 0; 
			private final int carbohydrate = 0; 
			
			// 필수 매개변수 생성자
			public Builder(int servingSize, int servings) {
				this.servingSize = servingSize;
				this.servings = servings
			}
			
			public Builder colories(int val){ colories = 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.caloris;
			fat = builder.fat; 
			sodium = builder.sodium; 
			carbohydrate = builder.carbohydrate;
			
		}
	}

NutritionFacts 클래스는 불변(final)이다. 이러한 빌더의 세터 메서드들은 자기 자신을 반환하기 때문에 연쇄적으로 호출할 수 있다.
이렇게 메서드 호출이 흐르듯 연결된다는 뜻으로 플루언트 API(fluent API) 혹은 메서드 연쇄(method chining)이라 한다.

profile
장생농씬가?

0개의 댓글