Effect Java 제2판(Joshua Bloch / Addison Wesley) 을 공부하고 정리한 내용 입니다.

2. 생성자의 매개 변수가 많을 때는 빌더(builder)를 고려하자.

Static 팩토리 메소드와 생성자는 공통적인 제약이 있다. 선택 가능한 매개변수가 많아질 경우 신축성 있게 처리하지 못한다는 것이다.

매개변수가 많은 생성자의 대안으로 텔리스코핑 생성자(telescoping constructor) 패턴과 자바빈즈(JavaBeans)패턴이 있다.
텔리스코핑 생성자 패턴은 매개변수의 수가 증가하면 클라이언트 코드 작서이 힘들고 코드의 가독성이 떨어지는 단점이 있고, 자바빈즈 패턴은객체가 일관된 상태를 유지하지 못할 수 있고, 스레드에서 안전성을 유지 하려면 프로그래머의 추가적인 노력이 필요하다는 단점이 있다.

2가지 패턴의 장점 결합한 텔리스코핑 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 결합한 방법이 있다.

빌더(Builder) 패턴형태로써, 원하는 객체를 바로 생성하는 대신 클라이언트는 모든 필수 매개변수를 갖는 생성자(또는 static 팩토리 메소드)를 호출하여 빌더 객체를 얻는다. 그 다음에 빌더 객체의 세터 메소드를 호출하여 필요한 선택 매개변수들의 값을 설정한다. 마지막으로, 클라이언트는 매개변수가 없는 builder 메소드를 호출하여 불변 객체를 생성한다. 빌더는 자신이 생성하는 객체의 클래스에 포함된 static 멤버 클래스이다.

빌더패턴 예제

 
// 빌더(Builder) 패턴
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 carbohydrate = 0;
		private int sodium = 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 carbohydrate(int val) {
			carbohydrate = val;
			return this;
		}
		
		public Builder sodium(int val) {
			sodium = 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;
	}
}

NutritionFacts는 불변 클래스이며, 모든 매개변수의 디폴트 값을 한군데에 모아 둔다는 것에 주목하자. 빌더의 세터 메소드들은 연속적으로 여러 번 호출될 수 있도록 빌더 자신의 객체를 반환한다. 

 
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8).calories(100).
				sodium(35).carbohydrate(27).build();

빌더 패턴의 장점

  1. 클라이언트 코드는 작성이 쉽고, 가독성이 좋다.
  2. 생성자처럼 빌더는 자신의 매개변수에 불변 규칙(invariants)을 적용할 수 있고 build 메소드는 그런 불변 규칙을 검사할 수 있다.
  3. 여러 개의 가변인자(varargs) 매개변수를 가질 수 있다.
  4. 유연성이 좋다. 하나의 빌더는 여러 개의 객체를 생성하는데 사용될 수 있으며, 이러한 과정 중에 빌더의 매개변수는 다양하게 조정될 수 있다.
  5. 매개변수들의 값이 설정된 빌더는 훌륭한 추상 팩토리를 만든다. 즉, 클라이언트 코드에서는 그런 빌더를 메소드로 전달하여 그 메소드에서 하나 이상의 객체를 생성하게 할 수 있다.
 
public interface Builder<T> { 
	public T build();	
}

 

단점

  1. 어떤 객체를 생성하려면 우선 그것의 빌더를 생성해야 한다. 성능이 매우 중요한 상황에서는 문제가 될 수 있다.
  2. 빌더 패턴은 텔리스코핑 패턴보다 코드가 길어지므로, 매개변수가 많을 때(4개 이상)만 사용하는 것이 좋다.
    그러나 향후에 매개 변수가 추가될 수 있다는 것을 염두에 두자

정리
생성자나 static 팩토리 메소드에서 많은 매개변수를 갖게 될 클래스를 설계할 때는 빌더 패턴이 좋은 선택이다. 특히 선택 매개변수가 대부분인 경우가 그렇다. 그리고 그간 사용하던 텔리스코핑 생성자 패턴보다 빌더를 사용하면 클라이언트 코드의 가독성이 좋고 작성도 쉽다. 또한 빌더는 자바빈즈 패턴보다 훨씬 더 안전하다.

Posted by outliers
,

Effect Java 제2판(Joshua Bloch / Addison Wesley) 을 공부하고 정리한 내용 입니다.

1. 생성자 대신 static 팩토리(factory) 메소드 사용을 고려하자.

장점

  1. 생성자와 다릴 자기 나름의 이름을 가질 수 있다는 것이다. 만일 생성자에 전달되는 매개변수가 반환 객체를 잘 나타내지 못한다면, 이름을 잘 지은 static 팩토리 메소드가 더 사용하기 쉬우며, 이 메소드를 호출하는 클라이언트 코드도 이해하기 쉽다.

    자바 클래스는 동일한 시그니처를 갖는 생성자를 하나만 가질 수 있다. 이런 제약을 해결하기 위해 타입이 다른 매개변수의 순서만을 바꾸어 두 개의 생성자로 만든다. 이 방법은 좋지 않다. 필요한 생성자가 어떤 것인지 기억하기도 어렵고, 실수로 다른 생성자를 호출 할 수도 있기 때문이다. 또한 그런 생성자를 사용하는 코드를 파악하려는 사람들은 문서를 봐야만 이해할 수 있다.

    하나의 클래스에 동일한 시그니처를 갖는 여러 개의 생성자가 필요한 경우에는 생성자 대신 static 팩토리 메소드를 사용하되, 메소드 간의 차이점을 부각시키도록 신중하게 이름을 선정한다.

  2. 생성자와 달리 호출될 때마다 매번 새로운 객체를 생성할 필요가 없다는 것이다. 따라서 불변(immutable)클래스의 경우 이미 생성된 인스턴스를 다시 사용할 수 있으며, 불필요하게 중복된 인스턴스들이 생성되는 것을 방지하기 위해 이미 생성된 인스턴스들을 저장했다가 반복 사용할 수 있다.

    동일한 객체가 자주 사용될 때 특히 객체 생성 시 자원이나 시간이 많이 든다면 프로그램 성능을 크게 향상시킬 수 있다.
    static 팩토리 메소드는 여러 번 호출되더라도 이미 생성된 동일 객체를 반환할 수 있으므로, 클래스에서는 언제든지 인스턴스들의 존재를 직접 제어 할 수 있다. 이런 일을 하는 클래스를 인서튼스 제어(instance-coltrolled) 클래스라고 한다. 인스턴스를 제어하면 싱글톤, 인스턴스 생성 불가 클래스로 만들 수 있다. 불변 클래스에서 두 개의 동일한 인스턴스가 생기지 않도록 해준다. 클래스에서 이것을 보장해 준다면 인스턴스의 동일 여부를 확인할 때 equals(Object) 메소드 대신 == 연산자를 사용할 수 있으므로 프로그램 성능도 향상될 수 있다. 열거형(enum)에서는 이것을 보장하고 있다.

  3. 자신의 클래스 인스턴스만 반환하는 생성자와 달리, static 팩토리 메소드는 자신이 반환하는 타입의 어떤 서브타입(subtype) 객체도 반환할 수 있다. 따라서 반환되는 객체의 클래스를 선택해야 할 때 뛰어난 유연성을 제공한다.

  4. 매개변수화 타입(parameterized type)의 인스턴스를 생성하는 코드를 간결하게 해준다는 것이다.

 

단점

  1. 인스턴스 생성을 위해 static 팩토리 메소드만 갖고 있으면서 public이나 protected 생성자가 없는 클래스의 경우는 서브 클래스를 가질 수 없다는 것이다.

    이런 단점으로 인해 상속 대신 컴포지션(composition)을 사용하게 끔 해주기 때문에 장점이 될 수도 있다.

  2. 다른 static 메소드와 쉽게 구별할 수 없다는 것이다.

    클래스나 인터페이스 주석으로 표시를 하거나, 공통적인 작명 규칙을 만들고 지킴으로써 이런 단점을 줄일 수 있다.
    작명 규칙 예) valueOf, of, getInstance, newInstance, getType, newType

 

static 팩토리 메소드와 public 생성자는 모두 나름의 용도가 있으므로 상호간의 장점을 아는 것이 중요하다.

프로그래밍 기술의 습득 또한 규칙을 먼저 배운 다음 그 규칙을 언제 깰 것인지 아는 것이 중요하다.

Posted by outliers
,