[Effective Java] 2. 생성자에 매개변수가 많다면 빌더를 고려하라

June Lee·2021년 4월 28일
0

Java

목록 보기
22/23

자바를 좀 더 잘 사용할 수 있는 방법이 없을까 고민하던 중, 자바 개발자들 사이에서는 이펙티브 자바라는 책이 거의 바이블처럼 읽힌다는 사실을 알게 되었다. 그래서 많이 부족하더라도, 여러 번 곱씹어 읽으면서 각 장의 내용을 내가 이해한 방식대로 정리해보려 한다.

우선 처음으로 정리할 장은 2장, 생성자에 매개변수가 많다면 빌더를 고려하라이다.

프로그램의 규모가 커지다보면, 한 클래스의 생성자에 매개변수가 많아지는 경우가 생긴다. 그런데 이 경우, 2가지 문제가 발생한다.

  1. 많은 매개변수 중 사용하지 않는 것이 많이 생긴다. (가독성의 문제)
  2. 어떤 매개변수의 집합으로 하나의 객체를 생성할 수 있게 할지에 대한 구분이 모호해진다. (안전성의 문제)
    • 예를 들어, 빵, 고기, 상추라는 필수 변수들이 필요한 햄버거 클래스가 있다고 할 때, 그냥 생성자 방식을 사용하면 빵, 상추만 넣은 햄버거를 만드는 것을 막을 수 없다는 문제가 발생한다.

이런 문제를 해결하기 위해 흔히 많은 자바 개발자들이 이용하는 방식은 크게 두 가지이다.

  1. 점층적 생성자 패턴(Telescoping Constructor Pattern)을 이용한다.
    : 원하는 매개변수를 갖는 생성자를 모두, 점층적으로 만들어준다.
  2. 자바빈즈 패턴(JavaBeans Pattern)을 이용한다.
    : 매개변수가 없는 생성자로 객체를 만든 후, setter를 이용해 값을 설정해준다.

먼저 1번 방식의 문제점은, 가독성이 낮다는 것이다. 다른 클래스에서 이 클래스를 인스턴스화하고 싶을 때에 매개변수가 몇 개인지 일일히 세어보아야하고, 몇 개의 매개변수일 때 어떤 의미의 인스턴스가 되는지 명확하게 보여줄 수 없다.

2번 방식의 문제점은, 객체가 완전히 생성되기 전까지는 일관성(Consistency)이 무너진 상태에 놓이게 된다는 것이다.
이런 단점을 보완하기 위해, 생성이 끝난 객체를 수동으로 얼리는 freeze 메서드를 사용하기도 하지만, 이 방법은 어려운데다가, 확실히 호출해줬는지 컴파일러가 보증할 방법이 없어서 런타임 오류에도 취약하다.

이런 문제점을 해결하기 위해 사용하는 것이 바로, 빌더 패턴(Builder Pattern)이다. 빌더 패턴은 점층적 생성자 패턴의 장점인 안전성, 그리고 자바빈즈 패턴의 장점인 가독성을 모두 취한 패턴이다.

public class NutritionFacts {
	private final int servingSize;
    private final int servings;
    private final int calories;
    
    public static class Builder {
    	//필수 매개변수
        private final int servingSize;
        
        //선택 매개변수
        private int servings = 0;
        private int calories = 0;
        
        public Builder(int servingSize) {
        	this.servingSize = servingSize;
        }
        
        public Builder servings(int val) {
        	this.servings = val;
            return this;
        }
        
        public Builder calories(int val) {
        	calories = val;
            return this;
        }
        
        public NutritionFacts build() {
        	return new NutritionFacts(this);
        }        
    }
    
    private NutritionFacts(Builder builder) {
    	servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
    
    }
}

위 코드에서 특이한 점은 Builder 클래스의 setter 메서드들이 Builder 객체 자체를 반환한다는 점이다. 이는 메서드 연쇄(Method Chaining)를 위한 장치이다.

즉 위와 같이 작성해줄 경우, 이를 사용하는 클라이언트 코드에서 다음과 같이 메서드를 연쇄적으로 호출해줄 수 있다.

NutritionFacts cocaCola = 
new NutritionFacts.Builder(240).servings(8).calories(100).build();

물론 실제로 사용할 때에는 빌더의 생성자 및 메서드에서 매개변수의 유효성을 검사해주는 등의 과정이 필요하다.


그러나, 위와 같은 빌더 패턴은 점층적 생성자 패턴에 비해 장황한 면이 있기 때문에, 생성자의 매개변수가 적어도 4개 이상일 때 검토해볼 만하다. 특히 해당 매개변수 중 대다수가 필수가 아닌 경우에는 더더욱 사용하는 것을 고민해보는 것이 좋다.

profile
📝 dev wiki

0개의 댓글