컴퓨터로 처리하는 데이터는 계속해서 복잡해져 왔고, 복잡한 프로세스를 가진 프로그램을 제작하기 위해 꽤 많은 기술과 방법들이 등장했습니다.


그 중 한가지는 OOP 로, 복잡한 데이터와 행위를 하나의 단위로 결합하여 관리함으로써 프로그램의 구조화 및 유지보수성을 키웠을 것이라 생각합니다.


이러한 가운데, 오늘 포스팅에서는 제작한 클래스의 내부 데이터(멤버 변수)들이 많고 이를 초기화(생성자 및 Setter 등)하는 과정이 생각보다 복잡할 때 사용하기 좋은 빌더 패턴(Builder Pattern)에 대해 다뤄보고자 합니다.


빌더 패턴을 이용하면, 객체 생성 시 초기화하는 과정을 보다 직관적이며 편리하게 이용할 수 있습니다.


예를들어, 아래와 같이 멤버변수가 많은 클래스가 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 빌더 패턴 테스트를 위한 VO
 *
 * Created by Doohyun on 2017. 7. 20..
 */
public class TestModelVo {
 
    private Integer field1;
    private Integer field2;
    private Integer field3;
    private Integer field4;
    private Integer field5;
    private Integer field6;
    private Integer field7;
    
    // SETTER, GETTER 생략
}
cs


이 클래스를 이용하여 객체를 제작해야할 경우, 각 멤버변수에 데이터를 넣는 방법은 다음과 같이 두 가지 방법을 생각해볼 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// setter 를 이용하는 방법.
{
    TestModelVo testModelVo = new TestModelVo();
    testModelVo.setField1(1);
    testModelVo.setField2(2);
    testModelVo.setField3(3);
    testModelVo.setField4(4);
    testModelVo.setField5(5);
    testModelVo.setField6(6);
    testModelVo.setField7(7);
}
 
// 한 번에 초기화할 수 있는 생성자를 제공하는 방법.
{
    TestModelVo testModelVo = new TestModelVo(1,2,3,4,5,6,7);
}
cs


모두 많이 사용하는 방법일 것이라 생각합니다.


첫 번째 방법은 Setter 를 이용하여 부분적으로 초기화를 수행하고 있습니다. 

보통 이렇게 많이 사용하는 편이지만, 초기화 과정이 한 단위 씩 끊어서 일어나고 있습니다.

(즉 객체는 이미 생성되었고 생성된 객체에 데이터를 넣는 과정. 일반적이며 나쁘지 않은 방법)


두 번째 방법은 데이터를 미리 받아, 객체를 생성할 수 있도록 생성자를 정의하는 방법입니다.

이 방법도 많이 사용하는 편이지만, 위의 예제와 같이 기화 값이 많을 경우 가독성 및 사용성이 떨어질 수 있습니다. 

(각 파라미터가 무엇인지, 제대로 값은 넣고 있는지, 등의 확인이 필요할 듯 합니다.)


또한 부분적으로 멤버변수를 초기화를 하고 싶은 경우, 그만큼의 생성자를 만들어야할 수 있습니다.



빌더패턴을 이용하면 복잡한 객체 생성 과정을 한 단위의 작업으로 다양하게 처리할 수 있으며, 이는 꽤 많이 사용될 여지를 줄 수 있습니다.


빌더패턴의 목적은 '복잡한 특정 객체의 생성 및 초기화할 수 있는 클래스의 제작'이며, 각 초기화 과정을 파이프라인 형식으로 사용할 수 있는 메소드를 제공하여 한 단위로 다양한 초기화 과정을 만들 수 있습니다.


파이프라인 형식은 일련의 행위를 수행한 후 자기 자신을 출력의 결과로 넘김으로써, 메소드를 파이프 연결 하듯이 계속 이어 사용할 수 있도록 하는 방법입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
/**
 * TestModelVo 를 생성할 수 있는 빌더.
 *
 * Created by Doohyun on 2017. 7. 20..
 */
public class TestModelVoBuilder {
 
    private TestModelVo testModelVo;
 
    private TestModelVoBuilder() {
        testModelVo = new TestModelVo();
    }
 
    /**
     * 빌더 인스턴스를 생성하는 정적 메소드.
     *
     * @return
     */
    public static TestModelVoBuilder Builder() {
        TestModelVoBuilder instance = new TestModelVoBuilder();
 
        return instance;
    }
 
    /**
     * 특정 초기화 작업 후, 자기자신을 결과를 넘김으로 파이프라인식의 메소드로 사용 가능.
     *
     * @param field1
     * @return
     */
    public TestModelVoBuilder setField1(Integer field1) {
        testModelVo.setField1(field1);
 
        return this;
    }
 
    /**
     * 필드2 초기화.
     *
     * @param field2
     * @return
     */
    public TestModelVoBuilder setField2(Integer field2) {
        testModelVo.setField1(field2);
 
        return this;
    }
 
    /**
     * 제작완료 후, 결과 VO 출력.
     *
     * @return
     */
    public TestModelVo build() {
        return testModelVo;
    }
}
 
// 한 단위로 복잡한 생성과정을 다양하게 만들 수 있어보임. (메소드를 이어 사용하는 파이프라인)
TestModelVo testModelVo = TestModelVoBuilder.Builder().setField1(1).setField2(2).build();
cs


이 방법을 이용하면, 한 단위로 다양한 생성자를 제작하는 것과 비슷한 효과를 볼 수 있습니다.

또한 파이프라인 형식으로 이용하기 때문에 편리하며, 메소드를 이용하기 때문에 초기화하는 방법이 직관적입니다.


이 방법은 설정과 관련된 객체를 생성하거나, 함수형 프로그래밍에서 일련의 쿼리를 만드는 등 여러 목적으로 많이 사용하고 있습니다.



예를들어, JAVA8 에서 등장한 Comparator::thenCompare 는 다음과 같은 원리로 만들어볼 수 있습니다.

(Comparator::thenCompare 는 정렬의 조건을 추가할 수 있는 메소드입니다.)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
 * thenCompare 기능을 지원하는 Comparator Builder
 *
 * Created by Doohyun on 2017. 7. 20..
 */
public class SimpleComparatorBuilder<T> {
    private Comparator<T> comparator;
 
    private SimpleComparatorBuilder() {
    }
 
    /**
     * 첫 초기화 시, 무조건 정렬조건을 받도록 함.
     *
     * - 적어도 한 조건을 받고, 다양한 비교조건을 제작.
     *
     * @param comparator
     * @param <T>
     * @return
     */
    public static <T> SimpleComparatorBuilder<T> Create(Comparator<T> comparator) {
        SimpleComparatorBuilder<T> simpleComparator = new SimpleComparatorBuilder<>();
 
        simpleComparator.comparator = comparator;
 
        return simpleComparator;
    }
 
    /**
     * 정렬의 조건을 추가하는 메소드.
     * 
     * @param inputComparator
     * @return
     */
    public SimpleComparatorBuilder<T> thenCompare(Comparator<T> inputComparator) {
        SimpleComparatorBuilder<T> simpleComparator = new SimpleComparatorBuilder<>();
 
        // 동적인 비교자 생성. 일련의 동적파라미터화.
        simpleComparator.comparator = (a, b) -> {
            // 기존 존재하는 비교자를 이용하여 비교.
            int compareResult = this.comparator.compare(a, b);
 
            if (compareResult == 0) {
                // 비교 결과, 두 객체가 동일할 경우 새로 입력된 비교 연산자를 이용하여 비교.
                return inputComparator.compare(a, b);
            } else {
                // 비교 결과가 다를 경우, 결과를 출력.
                return compareResult;
            }
        };
 
        // 새로 제작된 빌더를 출력.
        return simpleComparator;
    }
 
    /**
     * 조건에 대한 추가가 끝났을 경우, 내부의 비교자를 출력.
     * 
     * @return
     */
    public Comparator<T> build() {
        return comparator;
    }
}
cs


SimpleComparatorBuilder 의 목적은 비교자(Comparator)의 생성에 있어서, 비교하는 과정을 파이프라인 형식으로 다양하게 만들 수 있도록 thenCompare 기능을 제공하는 것입니다.


이를 이용하여, 다음과 같이 다양한 조건에 따라 비교하는 비교쿼리를 수행할 수 있습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
 * 비교구문을 테스트하는 메소드.
 */
public static void 비교_구문_테스트() {
    // 테스트를 위한 목록
    List<MemberVo> memberVos = Arrays.asList(
                                        new MemberVo("강XX"25)
                                        , new MemberVo("유XX"27)
                                        , new MemberVo("유XX"21));
 
 
    /**
     * 이름으로 내림차순
     * 나이로 오름차순.
     */
    Collections.sort(memberVos
                , SimpleComparatorBuilder.<MemberVo>Create((a, b) -> b.getName().compareTo(a.getName())).
                     thenCompare((a, b) -> a.getAge().compareTo(b.getAge())).build()
    );
 
    System.out.println(memberVos);
}
 
// CONSOLE LOG
// [{유XX, 21세}, {유XX, 27세}, {강XX, 25세}]
cs


다행히, 잘 작동하는 모듈이 만들어졌습니다. :-)

(시간이 괜찮다면, 이 글을 보고 직접 만들어보는 것을 추천합니다.)


이와 같이, 빌더패턴을 이용하면 단순히 객체 초기화 뿐만이 아닌 어떤 원하는 다양한 목적을 쉽게 구현할 수 있을 것입니다.

(Stream API 가 대표적이며, 이를 이용하여 Collection 에 대해 다양한 쿼리를 제작할 수 있습니다.)



"간단하지만, 쓸모가 적절한 이 패턴을 잘 알아두면 정말 좋지 않을까요?" 

라는 생각을 해보며, 이번 포스팅을 마칩니다.



이 글을 읽는 모두가 도움이 되었으면 좋겠습니다. :-)






반응형
Posted by N'