본문 바로가기
프로그래밍 놀이터/디자인 패턴, 리펙토링

[Design Pattern/Java] 생성자의 매개변수가 많을 때는 빌더(Builder) 를 고려하자.

by 돼지왕 왕돼지 2012. 2. 10.
반응형

안녕하세요 돼지왕왕돼지입니다.
오늘은 생성자의 매개변수가 많을 때 빌더 사용을 고려하자는 내용을 살펴보려고 합니다.
해당 글은 "Effective Java" 라는 책을 정리한 내용입니다.


매개변수가 많을 때 무엇이 문제가 되는가?


Static Factory Method 와 Constructor 의 공통적인 제약사항은 선택 가능한 매개변수가 많아질 경우 신축성 있게 처리하지 못한다는 문제가 있습니다.
모든 매개변수를 다 입력해야 한다면 상관 없지만, 몇개는 꼭 입력해야 하고 몇개는 선택해서 입력할 수 있는 경우를 말합니다.



해결책 1 : 텔레스코핑 생성자 ( Telescoping Constructor )


- 특징 : 매개변수 수를 달리하는 Constructor 들을 여러개 만들어 this( a, b, defaultValue ) 이런식으로 호출된 Constructor 안에서 다른 Constructor를 호출하며 전달받은 값에 default 값을 추가로 전달.

- 단점 : 매개변수의 수가 증가하면 무척 번거로워 집니다.
            생성자 작성에 대한 Cost 뿐만 아니라, 가독성까지도 떨어진다.
            코드를 파악하는 사람의 입장에서도, 그 값들이 어떤 의미인지를 알아야 하며, 갯수도 세어봐야 하는 단점이 있습니다.



해결책 2 : 자바빈즈 패턴 ( Java Beans Pattern )


- 특징 : 각 parameter 값에 해당하는 setter 를 미리 만들어 둡니다.
           Constructor 를 이용해 객체를 생성한 후에, setter methods 를 이용하여 값을 지정해줍니다.

- 잠점 : Telescoping constructor 에 비해 가독성이 늘어납니다.

- 단점: 여러번의 메소드 호출로 나누어져 인스턴스가 완성되므로, 생성과정을 거치는 동안 객체가 일관된 상태를 유지하지 못하는 경우가 발생하기 쉽습니다. 일관성 없는 상태의 객체를 사용하려 한다면 결함을 찾기 어려운 문제를 야기시킬 수도 있습니다. ( 예를 들어, a, b, c 값을 set 으로 지정해야 하는데, a, b, 만 set 하고, c 를 set 하기 전에 다른 thread 에서 해당 객체 사용하는 경우가 있을 수 있습니다. ) 다시 말해, 자바빈즈 패턴은 불변 ( immutable ) 클래스를 만들 수 있는 가능성을 배제하는 것입니다. ( 물론, thread-safe 를 유지할 순 있지만, 그만큼 프로그래머의 추가적인 노력이 필요하죠. )



해결책 3 : Builder 패턴 ( Telescoping Constructor 의 안정성과 Java Beans Pattern 의 가독성을 결합 )


 - 특징 : 원하는 객체를 바로 생성하는 대신, 필수 매개변수들를 parameter로 받는 builder의 constructor를 호출하여 빌더 객체를 얻습니다. ( Builder 객체는 보통 생성하고자 하는 class 의 sub class 로 존재합니다. )
             빌더 객체의 setter 메소드를 호출하여 선택 매개변수들의 값을 설정합니다.
             마지막으로 build 메소드를 호출하여 불변 객체를 생성합니다. ( 이 불변객체는 setter 가 없어서 불변입니다. )

 - 장점 : 생성객체를 불변으로 만들 수 있고, 코드 작성이 쉬우며, 가독성이 좋습니다.
            별도의  setter 메소드들을 사용하므로,  여러 개의 가변인자 매개변수를 가지는 효과를 낼 수 있습니다. 
            유연성이 좋습니다. ( 일부 필드의 값을 빌더에서 자동으로 설정할 수 있습니다. 예를 들면 자동으로 증가하는 일련번호.. )
            매개변수들의 값이 미리 설정된 빌더는 훌륭한 추상 팩토리 ( Abstract Factory ) 역할을 합니다.
            이렇게 하려면 type 이 필요한데, 제네릭 타입을 지정할 수 있는 코드면 충분합니다.
            ex ) public interface Builder<T>{
                        public T build();
                   }

         특정 빌더의 타입 매개변수로 "바운드 와인드카드 ( bounded wildcard ) 타입" 을 보통 사용합니다.
            ex ) Tree buildTree( Builder<? extends Node> nodeBuilder ){ ... }


 - 단점 :  어떤 객체를 생성하려면 우선 그것의 빌더를 생성해야 합니다.
              객체의 생성 비용이 눈에 띄게 클 정도는 아니어도, 성능이 매우 중요한 상황에서는 문제가 될 수 있습니다.
              텔레스코핑 패턴보다 코드가 길어지기 쉽습니다. ( 매개변수가 많을 때 ( 4개 이상일 때 )만 사용하는 것이 좋다. )


[Example Builder Code]

public class NutritionFacts{
     private final int servingSize;
     private final int servings;
     private final int calories;
     private final int fat;

     public static class Builder{
        private final int servingSize;
        private final int servings;

        private final int calories = 0;
        private final int fat = 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 NutritionFacts build(){
            return new NutritionFacts( this );
        }
   }

   private NutritionFacts( Builder builder ){
         servingSize = builder.servingSize;
         servings = builder.servings;
         calories = builder.calories;
         fat = builder.fat;
    }
}




반응형

댓글1