생성자에 매개변수가 많다!

taek·2021년 2월 14일
0

design-pattern

목록 보기
1/1
post-thumbnail

오늘은 생성자에 많은 매개변수가 있을 때 사용할 수 있는 패턴들에 대해 소개하고자 한다.
소개할 패턴들에서 사용할 클래스를 다음과 같다.

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, 
                          int calories, int fat, 
                          int sodium, int carbohydrate) {
        this.servingSize = servingSize;
        this.servings = servings;
        this.calories = calories;
        this.fat = fat;
        this.sodium = sodium;
        this.carbohydrate = carbohydrate;
    }
}

Telescoping constructor pattern

소개

  • 점층적 생성자 패턴이라고 한다.
  • 필수 매개변수들만 받는 생성자, 필수 매개변수들과 선택 매개변수 1개를 받는 생성자, 선택 매개변수 2개를 받는 생성자 등을 만든다. 이렇게 만들어서 모든 선택 매개변수들을 받는 생성자까지 만든다.
  • 아래는 생성자들 예시이다.
public NutritionFacts(int servingSize, int servings) {
    this.servingSize = servingSize;
    this.servings = servings;
}

public NutritionFacts(int servingSize, int servings, int calories) {
    this.servingSize = servingSize;
    this.servings = servings;
    this.calories = calories;
}

// ... ( 점층적으로 생성자에서 받는 매개변수의 수를 늘려간다. )

public NutritionFacts(int servingSize, int servings, 
                      int calories, int fat, 
                      int sodium, int carbohydrate) {
    this.servingSize = servingSize;
    this.servings = servings;
    this.calories = calories;
    this.fat = fat;
    this.sodium = sodium;
    this.carbohydrate = carbohydrate;
}
  • 클래스의 인스턴스를 만들려면 여러 생성자들 중에서 원하는 생성자를 골라서 호출하면 된다.

단점

  • 만약 servingSize, servings, fat, sodium 매개변수들을 가지는 인스턴스를 만들기 위해서는 아래의 생성자를 사용하고 calories 매개변수에 대해서는 0으로 최기화하여 만들어야한다.
public NutritionFacts(int servingSize, int servings, 
                      int calories, int fat, int sodium) {
    this.servingSize = servingSize;
    this.servings = servings;
    this.calories = calories;
    this.fat = fat;
    this.sodium = sodium;
}

// 새로운 인스턴스를 만들때
NutritionFacts example = new NutritionFacts(240, 8, 0, 20, 35);
  • 예시에서는 6개의 매개변수들을 사용하니 괜찮아 보일 수 있지만, 더 많은 매개변수들 가지게 된다면 클라이언트 코드 (생성자를 호출하는 코드)를 작성하거나 읽기 어렵다.
  • 코드를 읽을 때 각 매개변수 값들의 의미를 헷갈릴 수 있다.
  • 인스턴스 생성 코드 작성시 매개변수들의 위치를 바꿔도 컴파일러는 이를 알지 못합니다. 런타임에 가서야 오류가 있다는 것을 알 수 있다.

JavaBeans pattern

소개

  • 자바빈즈 패턴이라고 한다.
  • 매개변수가 없는 생성자로 객체를 만든 후, setter 메소드들을 호출해서 원하는 매개변수 값들을 설정하는 방식이다.
  • Telescoping constructor pattern의 단점이라고 할 수 있는 많은 매개변수를 가지는 생성자도 없고, 각 매개변수가 의미하는 것을 바로 알 수 있다.
public NutritionFacts() {}

public void setServingSize(int servingSize) { this.servingSize = servingSize; }
public void setServings(int servings) { this.servings = servings; }
public void setCalories(int calories) { this.calories = calories; }
public void setFat(int fat) { this.fat = fat; }
public void setSodium(int sodium) { this.sodium = sodium; }
public void setCarbohydrate(int carbohydrate) { this.carbohydrate = carbohydrate; }
  • 코드가 길어지긴 했지만 인스턴스를 만들기 쉽고, 그 결과 더 읽기 쉬운 코드가 되었다.
  • 사용 방법은 아래와 같다.
// 인스턴스를 만들고
NutritionFacts example = new NutritionFacts();

// 원하는 매개변수 값들을 삽입한다.
example.setServingSize(240);
example.setServings(8);
example.setCalories(100);
example.setSodium(35);
example.setCarbohydrate(27);

단점

  • JavaBeans pattern에서는 객체 하나를 만들려면 메소드를 여러 개 호출해야 하고, 객체가 생성되기 전까지는 일관성(consistency)이 무너진 상태에 놓이게 된다.
  • 일관성이 무너지는 문제 때문에 JavaBeans pattern에서는 클래스를 불변(immutable)으로 만들 수 없으며 스레드 안전성을 얻으려면 프로그래머가 추가 작업을 해줘야만 한다.

단점 보완

  • 단점을 완화하고자 생성이 끝난 객체를 수동으로 얼리고( freezing ) 얼리기 전에는 사용할 수 없도록 하기로 한다.
  • 이 방법을 쓴다고 하더라도 객체 사용전에 프로그래머가 freeze 메소드를 확실히 호출해줬는지를 컴파일러가 보증할 방법이 없어서 런타임 오류에 취약하다.
  • How do we freeze an object while constructing an object using JavaBeans pattern.
public class Foo {  
   
    private int a;  
    private int b;  
   
    private volatile Foo instance;  
   
    public int getA() { return a; }  
    public int getB() { return b; }  
   
    public void setA(int a) {  
        checkNotFrozen();  
        this.a = a;  
    }  
   
    public void setB(int b) {  
        checkNotFrozen();  
        this.b = b;  
    }   
   
    public Foo freeze() {
        instance = this;
        return instance;
    }  
   
    private void checkNotFrozen() { 
        if (instance == null) {
            throw new RuntimeException();
        }
    }
}

Builder pattern

소개

  • 빌더 패턴으로 불린다.
  • 클라이언트는 필요한 객체를 직접 만드는 대신, 필수 매개변수만으로 생성자( 혹은 정적 팩토리 메소드 )를 호출해 빌더 객체를 얻는다.
  • 빌더 객체가 제공하는 일종의 setter 메소드들로 원하는 선택 매개변수들을 설정한다.
  • 마지막으로 매개변수가 없는 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 int calories     = 0;
        private int fat          = 0;
        private int sodium       = 0;
        private int carbohydrate = 0;
        
        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings = servings;
        }
        
        public Builder calories(int calories) {
            this.calories = calories;
            return this;
        }
        
        public Builder fat(int fat) {
            this.fat = fat;
            return this;
        }
        
        public Builder sodium(int sodium) {
            this.sodium = sodium;
            return this;
        }
        
        public Builder carbohydrate(int carbohydrate) {
            this.carbohydrate = carbohydrate;
            return this;
        }
        
        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }
    
    private NutritionFacts(Builder builder) {
        this.servingSize = builder.servingSize;
        this.servings = builder.servings;
        this.calories = builder.calories;
        this.fat = builder.fat;
        this.sodium = builder.sodium;
        this.carbohydrate = builder.carbohydrate;
    }
}
  • Buildersetter 메소드들은 자기 자신을 반환하기 때문에 연쇄적으로 호출할 수 있다.
  • 메소드 호출이 흐르듯 연결되다는 뜻으로 fluent API 혹은 method chaining라 한다.
NutritionFacts example = new NutritionFacts.Builder(240, 8)
    .calories(100)
    .sodium(35)
    .carbohydrate(27)
    .build();
  • Builder pattern은 파이썬 및 스칼라에 있는 named optional parameters를 흉내낸 것이다.
  • 매개변수들을 검사하는 방법은 Builder의 생성자와 메소드에서 입력 매개변수를 검사하고, build 메소드가 호출하는 생성자에서 여러 매개변수에 걸친 불변식을 검사하자.
profile
'왜?'에 대답할 수 있는 사람이 되자!

0개의 댓글