[CHAPTER 13] Builder Pattern - Review
컴퓨터로 처리하는 데이터는 계속해서 복잡해져 왔고, 복잡한 프로세스를 가진 프로그램을 제작하기 위해 꽤 많은 기술과 방법들이 등장했습니다.
그 중 한가지는 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 에 대해 다양한 쿼리를 제작할 수 있습니다.)
"간단하지만, 쓸모가 적절한 이 패턴을 잘 알아두면 정말 좋지 않을까요?"
라는 생각을 해보며, 이번 포스팅을 마칩니다.
이 글을 읽는 모두가 도움이 되었으면 좋겠습니다. :-)
'스터디 > [STUDY] Effective OOP' 카테고리의 다른 글
[CHAPTER 18] Composite Pattern - Review (0) | 2017.09.03 |
---|---|
[CHAPTER 16] Iterator Pattern - Review (0) | 2017.08.02 |
[CHAPTER 11] State Pattern - review (1) | 2017.07.12 |
[CHAPTER 10] Command Pattern - review (0) | 2017.07.06 |
[CHAPTER 7] Facade Pattern - review (0) | 2017.06.29 |