정적 팩토리 메소드와 생성자에는 공통적으로 취약점이 하나 있다. 바로 선택적 매개변수가 많을 경우 적절히 대응하기 어렵다는 것이다. 또한 중복되는 생성자를 오버로딩하다보면 개발자의 피로도가 증가하게 된다.

Solution 1 Java beans Pattern

public class NutritionFacts{
...
	public void setServingSize(int val){ this.servingSize = val; }
	public void setServings(int val){ this.servings = val; }
	public void setCalories(int val){ this.calories = val; }
	public void setFat(int val){ this.fat = val; }
	public void setSodium(int val){ this.sodium = val; }
	public void setCarbo(int val){ this.carbo = val; }
...
}

인스턴스를 만들기 쉽고 더 읽기 쉬운 코드가 되었으나, 객체 하나를 만들기 위해 메서드를 여러개 호출해야한다는 단점이 있다. 또한 객체가 완전히 생성되기 전 까지 일관성이 무너진 상태에 놓이게 되며, 개발자가 하나하나 프로퍼티를 직접 지정해줘야 한다는 단점이 있다.

Solution2 Builder Pattern

빌더 패턴은 전통적 생성자 패턴의 안정성과 자바빈즈패턴의 가독성을 겸비하였다.

빌더패턴에서 클라이언트는 필요한 객체를 직접 만드는 것이 아니라 필수 매개변수 만으로 생성자를 호출하여 빌더객체를 얻는다. 그런 다음 빌더객체가 제공하는 일종의 setter메소드들로 원하는 선택 매개변수를 설정하게 된다.

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 carbo;
	
	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 carbo = 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 sodium(int val){
			sodium = val;
			return this;
		}
		public Builder carbo(int val){
			carbo = val;
			return this;
		}		
	}
	private NutritionFacts(Builder builder){
		servingSize = builder.servingSize;
		servings = builder.servings;
		calories = builder.carlories;
		fat = builder.fat;
		sodium = builder.sodium;
		carbo = builder.carbo;
	}
}
NutritionFacts cocaCola = new NutritionFacts.Bilder(240,8)
															.calories(100)
															.sodium(35)
															.carbo(27)
															.build()

빌더패턴은 위처럼 필수 매개변수를 제외한 선택적 매개변수를 클라이언트에 위임하게 된다.

다만 이때, 잘못된 매개변수를 일찍 발견하는 코드가 작성되지 않았는데, 실무에서는 빌더의 생성자와 메서드에서 입력 매개변수를 검사하는 로직을 필히 넣어야 한다. 만약 어떤 매개변수가 잘못된 경우 IllegalArgumentException 으로 예외를 던져주자

빌더패턴의 장점과 Lombok

  1. 빌더 패턴은 계층적으로 설계된 클래스와 함께 사용하기 좋다.

  2. lombok을 사용하면 복잡한 빌더 코드를 간결하게 작성할 수 있다.

    @Getter
    @AllArgsConstructor(access = AccessLevel.PRIVATE)
    @Builder(builderMethodName = "memberBuilder")
    public class Member{
    	private long id;
    	private String email;
    	private String name;
    	private LocalDateTime createdAt;
    	private LocalDateTime updatedAt;
    	private List<Coupon> coupons = new ArrayList<>();
    	
    
    	public static MemberBuilder builder(String email){
    		if(email == null){
    			throw new IllegalArgumentException("이메일이 누락되었습니다");
    		}
    		return memberBuilder().email(email);
    	}
    }
    

    위처럼 lombok라이브러리를 활용하면 보다 간편하게 빌더 패턴을 적용할 수 있다. 또한 클래스내부에 builder 메서드를 정의하여 필수로 들어가야할 필드를 검증하는 로직을 작성할 수 있다.

    참고로 lombok의 @Builder어노테이션은 전체 인자를 갖는 생성자를 자동으로 만들어준다. 따라서 빌더패턴을 완전히 적용하기 위해 @AllArgsConstructor의 어노테이션에 접근자를 private로 만들어 외부에서 접근할 수 없도록 해주자