빌더 패턴
의도
복잡한 객체를 생성하는 방법과 표현하는 방법을 정의하는 클래스를 별도로 분리한다.
⇒ 객체 생성과 조립 알고리즘의 분리
활용성
- 복합 객체의 생성 알고리즘이 이를 합성하는 요소 개체들의 조립 방법에 독립적일 때
- 합성할 객체들의 표현이 서로 달라도 생성 절차에서 이를 지원해야 할 때
구조
* Builder : 객체의 일부 요소들을 생성하기 위한 추상 인터페이스를 정의
* ConcreteBuilder : 빌더 인터페이스를 구현하며 제품의 부품들을 모아서 요소들을 모아서 Product를 생성한다.
* Director : 빌더 인터페이스를 사용하는 객체
* Product : 생성할 객체
클라이언트는 Director 객체를 생성하고 생성된 Director는 원하는 빌더 객체를 통해서 Product에 필요한 요소를 추가한다. 최종적으로 클라이언트에서는 빌더 객체에서 Product를 가져온다.
장점과 단점
- (+) Product에 대한 내부 표현을 다양하게 변화할 수 있다. 빌더 인터페이스를 사용함으로써 실제 사용되는 요소가 무엇인지를 외부에는 숨길 수 있다. 또한 Product의 요소 복합 방법이 바뀐다면 해당 인터페이스를 구현하는 서브클래스를 새로 정의하면 된다
- (+) 생성과 표현에 필요한 코드를 분리해서 관리할 수 있다. 실제 객채를 생성하는 부분과 필요한 요소를 조립하는 부분을 분리하기 때문에 Product의 내부 요소를 정의한 클래스를 따로 알 필요 없이 빌더를 통해서 필요한Product를 가져올 수 있다
- (+) 불완전한 객체를 생성하지 못하게 끔 validate 할 수 있다
- (-) 원하는 객체 생성을 위해서 Director, ConcreteBuilder 객체를 미리 생성해야 한다
코드 예시
Director가 요청하는 각각의 요소를 생성하는 연산은 빌더 인터페이스에 구현한다. 그리고 이를 구현하는 서브클래스 concreteBuilder에서 실제로 필요한 요소의 생성 로직을 구현한다.
* 빌더 인터페이스
public interface TourPlanBuilder {
// TourPlanBuilder를 리턴함으로써 메서드 체이닝이 가능하도록 함
TourPlanBuilder title(String title);
TourPlanBuilder nightsAndDays(int nights, int days);
TourPlanBuilder whereToStay(String whereToStay);
// 최종적으로 생성할 인스턴스의 상태 검증도 가능
TourPlan getPlan();
}
* 빌더 구현체
public class DefaultTourPlanBuilder implements TourPlanBuilder{
private String whereToStay;
private String title;
private Integer nights;
private Integer days;
@Override
public TourPlanBuilder title(String title) {
this.title = title;
return this;
}
@Override
public TourPlanBuilder nightsAndDays(int nights, int days) {
this.nights = nights;
this.days = days;
return this;
}
@Override
public TourPlanBuilder whereToStay(String whereToStay) {
this.whereToStay = whereToStay;
return this;
}
@Override
public TourPlan getPlan() {
// 필요하다면 여기서 validate
return new TourPlan(whereToStay, title, nights, days);
}
}
* Director
/* 자주 사용하는 빌더를 director클래스에 모아두고 사용할 수 있음 */
public class TourDirector {
private TourPlanBuilder builder;
public TourDirector(TourPlanBuilder builder){
this.builder = builder;
}
public TourPlan defaultTrip(){
return builder.title("title")
.whereToStay("seoul")
.nightsAndDays(2,3)
.getPlan();
}
}
* 실제 사용
public static void main(String[] args) {
// director를 통해서 객체를 받아옴
TourDirector director = new TourDirector(new DefaultTourPlanBuilder());
System.out.println(director.defaultTrip());
}
실제 사용되는 빌더 패턴의 예시
GoF에 비해서 이펙티브 자바에서 말하는 빌더패턴은 좀 더 실제 사용성에 무게를 둔다. 코드의 가독성, 유지보수가 편리해지므로 빌더패턴 사용을 권한다.
아래는 자주 사용되는 빌더패턴 구현코드이다.
참고 : https://johngrib.github.io/wiki/builder-pattern/
// Effective Java의 Builder Pattern
public class NutritionFacts {
private final int servingSize;
private final int servings;
private final int calories;
private final int fat;
private final int sodium;
private final int carbohydrate;
public static class Builder {
// Required parameters(필수 인자)
private final int servingSize;
private final int servings;
// Optional parameters - initialized to default values(선택적 인자는 기본값으로 초기화)
private int calories = 0;
private int fat = 0;
private int carbohydrate = 0;
private int sodium = 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 Builder carbohydrate(int val) {
carbohydrate = val;
return this;
}
public Builder sodium(int val) {
sodium = 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;
sodium = builder.sodium;
carbohydrate = builder.carbohydrate;
}
}
'디자인패턴' 카테고리의 다른 글
[GoF의 디자인 패턴] 프로토타입 패턴 (0) | 2022.03.09 |
---|---|
[GoF의 디자인 패턴] 추상 팩토리 패턴 (0) | 2022.03.04 |
[GoF의 디자인 패턴] 싱글톤 패턴 (0) | 2022.03.01 |
[GoF의 디자인 패턴] 어댑터 패턴 (0) | 2021.11.21 |
[GoF의 디자인 패턴] 전략 패턴 (0) | 2021.11.18 |