QueryDSL에서의 빌더패턴의 예시
Member findMember = queryFactory.select(m)
.from(m)
.where(m.username.eq("member1"))
.fetchOne();
쿼리 DSL에서 비슷한 패턴으로 JPQL을 편리하게 이용하는 과정을 목도했는데 Method Chaining이 다소 생소하긴 했는데 이번에 빌더패턴을 공부하게 되면서 좀 더 깊이있는 사고를 하게 된 것 같다.
(정확히 말하면 DSL은 메서드체이닝 방식을 이용한거고,
빌더패턴도 메서드 체이닝을 이용해서 구현한 전략이긴 하다.)
Builder Pattern
동일한 프로세스를 거쳐서 다양한 구성의 인스턴스를 만드는 방법으로 복잡한 객체를 만드는 프로세스를 독립적으로 분리할 수 있다는 장점이 있다.
이번에 진행한 실습을 클래스 다이어그램으로 간단하게 나타내보았다
TourDirector
로 반복되는 호출에 관한 데이터 스택을 따로 숨길 수 있고
여러개의 DefaultTourBuilder
의 구현을 통해 다양한 구성의 인스턴스를 만들 수 있다
여기서는 TourPlan
에서 여러가지 경우의 수에 따라 다른 DefaultTourBuilder
를 생성함으로써 여러 생성자를 만들어야하는 경우 생성자의 길이가 늘어나거나 검증 로직을 별도로 추가할 수 없는(너무 복잡해짐 + SRP 위반) 한계를 마주하는데 빌더패턴으로써 이 한계점을 돌파할 수 있다.
왜 TourPlanBuilder 인터페이스의 메서드 반환형은 TourPlanBuilder인가?
Method Chaining을 통해 다음과 같은 방식으로 객체 인스턴스 필드에 값을 연속적으로, 순서대로 지정하고 특정 메서드의 호출로 완성된 객체 인스턴스를 반환받을 수 있기 때문이다.
이러한 패턴을 이용하여, 우리는 어떤 필드를 검증하거나 특정 순서대로 객체 인스턴스의 필드에 값을 배정하는 등 부가적인 기능을 담을 수 있다.
GPT에게 Product와 Builder의 객체간 관계가 포함관계에 해당하는지 궁금해져서 물어봤더니 아니라고 한다.
단순히 객체를 담는 필드가 클래스 내에 있다고 해서 강한 결합관계에 있다고 가정하면 큰일날 성 싶다.
빌더 패턴의 장단점
장점
- 메서드 체이닝 방식을 사용하고 있어서 복잡한 객체를 순차적으로 만들 수 있다.
- 복잡한 객체를 만드는 구체적인 과정을
Director
객체를 통해 추상화하여 클라이언트로부터 숨길 수 있다. - 동일한 프로세스를 통해 각기 다르게 구성된 객체를 만들 수 있다.
- 세부적인 플랜의 추가는 어떤 빌더를 사용하느냐에 따라 다르게 생성할 수 있다.
- 과정은 비슷한데 다른 인스턴스가 나오는게 가능한것에 집중하자
- 불완전한 객체를 사용하지 못하도록 방지할 수 있다.
- 별도의 검증로직을 메서드에 추가하여
getInstance()
로 만든 인스턴스를 반환할때 별도의 검증 로직을 추가할 수 있다 - 이는 검증 로직을 생성자에 별도로 결합할 수 없기 때문에(너무 복잡해짐) 오히려 코드의 복잡성을 떨어트리는데는 도움이 될 수 있다.
- 별도의 검증로직을 메서드에 추가하여
단점
- 원하는 객체를 만들려면 빌더 클래스를 제작해주어야 한다.
- 클래스를 여러개 더 만들어야 하기 때문에 클래스 계층구조가 원래보다 더 복잡해질 수 있다.
빌더 패턴이 쓰이는 곳
자바 8 Stream Builder API
여기서 사실 제너릭에 관련된 개념이 조금 헷갈리는게 있었다
https://chat.openai.com/share/71c8f076-9b84-4347-a74d-feed479997f5
이걸 보면 어떤 대화가 오고 갔는지 대충 볼 수 있는데, 하나만 끌어와보자면,
??? : 런타임에 타입 체크 자동으로 된다면서요
List<String> arrList = new ArrayList<>();
이건 되는데
Stream<String> names = Stream.<>builder.add("hello").build();
이건 왜 자동으로 inference(유추)가 안되는 거임? 왼쪽에 줬잖아요라고 질문했다.
아직도 잘 이해가 안가서 메모해놓는다…
하고 답을 찾았다 객체를 만드는 과정에서 메서드 체이닝이 일어나는데 메서드 체이닝 과정 전에 Type이 확정되는 것이 아니기 때문에 에러가 발생할 수 있다고 한다. 따라서 Explicit하게 별도로 타입을 명시해주어야한다고 하네요!
그 외에도 Lombok
의 Builder
, 스프링의 UriComponentsBuilder
, MockMvcWebClientBuilder
등 다양한 빌더 패턴을 이용한 편의기능이 사용되고 있다.
UriComponents
를 통해 UriComponentsBuilder
로 부터 URI를 받아오는 URI를 문자열로 만드는것보다 안전하다 공백, 인코딩을 URI에 쓰기 안전한 형태로 반환해준다.
'전공 > Design Pattern' 카테고리의 다른 글
Design Pattern - Prototype Pattern (2) | 2023.10.10 |
---|---|
Design Pattern - Abstract Factory Pattern (0) | 2023.10.07 |
Design Pattern - Factory Method Pattern (0) | 2023.10.07 |
Design Pattern - Singleton Pattern (1) | 2023.10.05 |