[effective java] (2) 많은 parameter가 있는 constructor은 builder 패턴을 고려하라

orca·2022년 10월 28일
0

effective java

목록 보기
2/8

자바의 본질을 실무에 적용하는 이펙티브 자바 강의를 듣고 정리한 내용입니다

많은 parameter가 있는 클래스의 인스턴스를 만들어야 하는 경우

또 어떤 건 필수 parameter인데 어떤 건 선택적 parameter라면?

public class NutritionFacts {
    private final int servingSize; //필수
    private final int servings; //필수
    private final int calories;
    private final int fat;
    private final int solium;
    private final int carbonhydrate;
    }

여러개의 생성자를 두는 방법

아래와 같이 순차적으로 생성자를 두는 방식

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

    public NutritionFacts(int servingSize, int servings, int calories, int fat, int solium){
        this(servingSize, servings, calories, fat, solium, 0);
    }

    public NutritionFacts(int servingSize, int servings, int calories, int fat){
        this(servingSize, servings, calories, fat, 0);
    }

생성자는 필드가 많아질수록 호출하기 복잡해질 것
또 만약에 7번째 필드가 추가된다면? 수정이 번거로우며 IDE가 잡지 못하는 에러가 발생할 가능성이 있음

Java Beans Pattern 활용

필수 변수는 생성자로, 선택적 변수는 setter을 활용할 수 있게 구현하자

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

    public void setCalories(int var){
        this.calories = var;
    }

그러나 setter은 코드라인이 늘어나는 경향이 있음
또 객체가 완결성이 있기 전까지는 미완결인 상태라는 것이 불안 요소

NutritionFacts nutritionFacts = new NutritionFacts(1, 1);
nutritionFacts.setCalories(1);
nutritionFacts.setFat(1);

그래서 Builder 패턴을 사용한다

Builder 직접 구현

어떤 흐름으로 구현이 되는지 살펴보자

public class NutritionFacts {

...
    
    public static class Builder{
        private final int servingSize;
        private final int servings;
        private int calories = 0;
        private int fat = 0;
        private int solium = 0;
        private int carbonhydrate = 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 solium(int val){
            solium = val;
            return this;
        }

        public Builder carbonhydrate(int val){
            fat = val;
            return this;
        }

        //마지막으로 build()라는 메서드를 호출해서 상위가 가지고 있는 새로운 객체를 리턴함
        public NutritionFacts build(){
            return new NutritionFacts(this);
        }
    }

    public NutritionFacts(Builder builder) {
        this.calories = builder.calories;
        this.servings = builder.servings;
        this.servingSize = builder.servingSize;
        this.fat = builder.fat;
        this.solium = builder.solium;
        this.carbonhydrate = builder.carbonhydrate;
    }
 }

1️⃣ 생성자를 이용해 필수 변수를 받은 Builder 생성

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

2️⃣ 빌더 체인을 통해 선택적으로 값을 주입
유의해야 할 부분은 이때 Builder가 자기 자신을 return 한다는 것

 public Builder calories(int val){
            calories = val;
            return this;
        }

3️⃣ build를 통해 Builder을 상위 클래스 호출함

 public NutritionFacts build(){
            return new NutritionFacts(this);
        }

상위 클래스는 Builder 클래스를 생성자로 받아 필드를 채움

public NutritionFacts(Builder builder) {
        this.calories = builder.calories;
        this.servings = builder.servings;
        this.servingSize = builder.servingSize;
        this.fat = builder.fat;
        this.solium = builder.solium;
        this.carbonhydrate = builder.carbonhydrate;
    }
 }

아래와 같이 인스턴스를 만든다

NutritionFacts nf = new NutritionFacts.Builder(0,0).calories(1).fat(1).build();

물론 lombok을 활용해 간단하게 builder 생성할 수 있음

0개의 댓글