Builder 패턴

5.디자인패턴 2016. 9. 10. 17:36

빌더 패턴은 객체의 생성과 객체 생성 시 필요한 매개 변수 설정 과정을 분리함으로써 다음과 같은 이점을 확보해 주는 패턴이다.

1. 매개 변수가 많은 경우(특히 연속된 동일 타입의 매개 변수를 설정할 경우)에 발생할 수 있는 설정 오류를 방지할 수 있는 가독성을 제공한다.

2. 디폴트 값이 존재하는 매개 변수를 생략할 수 있도록 한다.

3. (immutable 변수나 final 키워드가 있는 변수처럼) 꼭 생성자를 통해서 설정 되어야 하는 변수를 지원할 수 있는 방법을 제공한다.


최종 결과물

class Hero{

    private int exp;

    private int cash0;

    private final String name; // 생성자를 통해 설정되어야 할 변수

    private final int level;   // 생성자를 통해 설정되어야 할 변수

    private Hero(Builder builder){

        this.exp = builder.exp;

        this.cash = builder.cash;

        this.name = builder.name;

        this.level = builder.level;

    }

    public void print(){

        System.out.println(exp + " " + cash + " " + name + " " + level);

    }

    static class Builder{

        private int exp = 0;

        private int cash = 0;

        private final String name;

        private final int level;

        public Builder(String name, int level){

            this.name = name;

            this.level = level;

        }

        public Builder exp(int exp){ this.exp = exp; return this;}

        public Builder cash(int cash){ this.cash = cashreturn this;}

        public Hero build(){

            return new Hero(this);

        }

    }

} 


실행 방법(객체 생성)

public static void main(String[] args) {

    Hero hero = new Hero.Builder("ceasar", 10).cash(20).exp(20).build();

    hero.print();

}


이 예제에서는 Hero라는 객체를 생성하고자 한다. Hero는 총 4개의 변수를 가지고 있고, 이 중에서 name과 level은 꼭 생성자를 통해서 설정 되어야 하는 변수이다. 그리고 나머지 exp와 cash 변수는 디폴트 값인 0을 가지고 있다.

따라서 다음과 같은 경우의 수를 가지고 있다.

1. name과 level만을 매개변수로 받아 생성하는 경우

2. name + level + exp

3. name + level + cash

4. name + level + exp + cash


만약 이러한 조합을 Telescoping Parameter Pattern으로 구현한다면 총 4개의 생성자를 구현해야 한다. 여기서 매개 변수가 더 추가된다면 생성자 수는 조합적으로 증가하게 될 것이다.

또한 level과 exp, 그리고 cash 변수는 모두 int 타입이다. 만약 단일 생성자에서 4개의 변수를 모두 설정한다면

    생성자("이름", 1, 2, 3);

과 같은 형식으로 변수 값을 할당하게 될 것이다. 하지만 이 경우 생성자의 변수 입력 순서를 정확히 알고 있지 않다면 여러 값을 넣으면서 착오에 의한 에러를 발생시킬 수 있다. 그리고 코드만으로는 쉽게 오류를 찾아내기가 힘들다.

이런 문제점을 해결하는 것이 빌더 패턴이다. 우선 Hero 클래스를 만들어 보자.

class Hero{

    private int exp;

    private int cash;

    private final String name; // 꼭 생성자를 통해 설정되어야 할 변수

    private final int level;   // 꼭 생성자를 통해 설정되어야 할 변수

간단히 변수만 선언한 모습이다. 빌더 패턴에서는 생성자에 바로 매개 변수를 입력하도록 하지 않고, 내부 클래스인 빌더 클래스를 이용하게 되어 있다. 빌더 객체인 Builder는 Hero의 내부 클래스로 선언이 된다. 그리고 외부 클래스인 Hero 객체를 생성하는 것이므로 Hero 객체가 없이 Builder 객체를 생성, 이용할 수 있도록 static 클래스로 선언해 주어야 한다. 그 모양은 아래와 같다.

class Hero{

    private int exp;

    private int cash;

    private final String name; // 생성자를 통해 설정되어야 할 변수

    private final int level;   // 생성자를 통해 설정되어야 할 변수

    static class Builder{

        private int exp = 0;

        private int cash = 0;

        private final String name;

        private final int level;

    }

}

일단 Builder 클래스가 가지고 있는 내부 변수는 모두 Hero가 가지고 있는 것과 일치한다. 여기서 중요한 점은 Builder 객체가 가지고 있는 변수 값이 Hero 객체의 초기값이 될 예정이므로 Builder 객체가 가진 변수들의 초기값을 원하는 값으로 맞춰 주어야 한다는 점이다. 즉, exp와 cash는 0이라는 초기값을 가지고 있는데, 이 초기값을 Hero가 아닌 Builder 클래스의 초기값으로 설정해 주어야 한다. 그래야 나중에 Builder 객체의 초기값이 Hero에 덮어 씌워지면서 원하는 초기값을 가질 수 있게 된다.

다음으로는 생성자를 만들 차례다. 생성자는 Builder 객체가 제공해 준다. 이 때 Hero의 변수 중 name과 level이 final로 설정되어 있으므로, 이 두 변수를 필수적으로 설정해 주도록 생성자에 매개 변수를 선언해야 한다. 구현을 해 보면 다음과 같다.

class Hero{

    private int exp;

    private int cash;

    private final String name; // 꼭 생성자를 통해 설정되어야 할 변수

    private final int level;   // 꼭 생성자를 통해 설정되어야 할 변수



    static class Builder{

        private int exp = 0;

        private int cash = 0;

        private final String name;

        private final int level;


        public Builder(String nameint level){

            this.name = name;

            this.level = level;

        }

    }

}

위와 같이 Builder의 생성자를 선언하여 name과 level을 매개 변수로 입력하도록 강제 할 수 있다.

다음은 옵션으로 입력 받을 수 있는 exp와 cash 변수에 대한 setter를 선언한다. 일반적인 setter 함수는 set+변수명 형식이지만 Builder 패턴에서는 가독성을 좋게 하면서도 setter와의 다른 특성을 가지고 있는 점을 알리기 위해서 변수명 그대로를 setter 이름으로 사용한다.(Java에서는 이것이 가능하지만 다른 언어라면 언더 바('_') 등을 활용할 수 있다.) 이를 선언해 보면 아래와 같다.

class Hero{

    private int exp;

    private int cash;

    private final String name; // 꼭 생성자를 통해 설정되어야 할 변수

    private final int level;   // 꼭 생성자를 통해 설정되어야 할 변수



    static class Builder{

        private int exp = 0;

        private int cash = 0;

        private final String name;

        private final int level;


        public Builder(String nameint level){

            this.name = name;

            this.level = level;

        }


        public Builder exp(int exp){ this.exp = exp; return this;}

        public Builder cash(int cash){ this.cash = cashreturn this;}

    }

}

여기서 주목할 부분은 각 함수 마지막 구문인 return this;이다. 여기서 this는 Builder 객체를 말한다. Builder 객체 자신을 리턴함으로써 생성자 호출 후 옵션 변수 setter 함수들을 연속적으로 호출할 수 있다. 가령

Builder("이름", 10).exp(값).cash(값).... 형태로 연속 호출이 가능하다는 말이다. 이를 통해 각 변수 값이 어떤 변수에 셋팅되게 되는지를 쉽게 알 수 있게 된다.

Builder 클래스에서는 최종적으로 build() 함수를 제공해 주어야 한다. build() 함수는 생성자와 setter를 통해 설정된 매개 변수들을 이용하여 Hero 객체를 생성하는 함수이다.

class Hero{

    private int exp;

    private int cash;

    private final String name; // 꼭 생성자를 통해 설정되어야 할 변수

    private final int level;   // 꼭 생성자를 통해 설정되어야 할 변수



    static class Builder{

        private int exp = 0;

        private int cash = 0;

        private final String name;

        private final int level;


        public Builder(String nameint level){

            this.name = name;

            this.level = level;

        }


        public Builder exp(int exp){ this.exp = expreturn this;}

        public Builder cash(int cash){ this.cash = cash;  return this;}


        public Hero build(){

            return new Hero(this);

        }

    }

}

이제 build() 함수가 호출하는 Hero 클래스의 생성자를 만들어 주어야 한다. build() 함수 내에서의 Hero 생성자는 this, 즉 Builder 객체를 받도록 되어 있다. 따라서 Builder 객체를 받는 생성자를 선언해 주어야 한다. 그리고 Builder 객체가 가지고 있는 변수 값들을 모두 가지고 와서 자신의 변수 값으로 셋팅하는 과정을 포함해야 한다.

class Hero{

    private int exp = 0;

    private int cash = 0;

    private final String name; // 생성자를 통해 설정되어야 할 변수

    private final int level;   // 생성자를 통해 설정되어야 할 변수


    static class Builder{

        private int exp;

        private int cash;

        private final String name;

        private final int level;

        public Builder(String name, int level){

            this.name = name;

            this.level = level;

        }

        public Builder exp(int exp){ this.exp = exp; return this;}

        public Builder cash(int cash){ this.cash = cashreturn this;}

        public Hero build(){

            return new Hero(this);

        }

    }


    private Hero(Builder builder){

        this.exp = builder.exp;

        this.cash = builder.cash;

        this.name = builder.name;

        this.level = builder.level;

    }

    public void print(){

        System.out.println(exp + " " + cash + " " + name + " " + level);

    }

}

위와 같이 Hero 클래스의 생성자를 구현해 주었다. 그리고 추가로 내부 변수 값을 확인할 수 있는 print() 함수를 구현해 주었다. 실행은 글의 첫머리에 나오는 실행 함수를 실행해 보면 된다.

이처럼 Builder 패턴은 객체 생성시 초기 설정 값들에 의해 발생할 수 있는 여러 문제점들을 해결해준다. 종종 setter 함수의 경우에는 가독성 지원 문제를 해결하기 위해 생성 과정과는 별도로 구현해서 사용하기도 한다.

'5.디자인패턴' 카테고리의 다른 글

Template Method 패턴  (0) 2016.09.10
Singleton 패턴  (0) 2016.09.10
Holder 패턴  (0) 2016.08.28
Adapter 패턴  (0) 2016.08.23
Decorator 패턴(synchronizedList의 구현 패턴)  (0) 2016.08.23
Posted by 이세영2
,