Builder

DongHeon·2022년 11월 7일
0

디자인 패턴

목록 보기
4/12

GoF 생성 패턴 중 하나인 Builder에 대해 알아보겠습니다.

Builder?

인스턴스를 생성하다 보면 다양한 구성이 필요한 경우가 있습니다. 모든 프로퍼티 값이 필요한 인스턴스도 있고 그렇지 않은 인스턴스도 존재합니다.

Builder 패턴은 인스턴스를 만드는 과정을 동일한 프로세스를 통해 해결하는 패턴입니다.

Client 쪽에서 직접 Builder를 사용할 수도 있지만 Director를 통해 builder를 사용하면 반복되는 builder 호출을 숨길 수 있습니다.

코드

패턴 적용 전

class TourPlan {
    var title: String?
    var days: Int?
    var lodging: String?
    var plan: [Int: [String]] = [:]
    
    init(title: String?, days: Int?, lodging: String?) {
        self.title = title
        self.days = days
        self.lodging = lodging
    }
    
    convenience init() {
        self.init(title: nil, days: nil, lodging: nil)
    }
    
    convenience init(title: String, days: Int) {
        self.init(title: title, days: days, lodging: nil)
    }
    
    func setTitle(title: String) {
        self.title = title
    }
    
    func setDays(days: Int) {
        self.days = days
    }
    
    func bookLodging(lodging: String) {
        self.lodging = lodging
    }
    
    func addPlan(day: Int, detailPlan: String) {
        if self.plan[day] == nil {
            self.plan[day] = []
        }
        
        self.plan[day]?.append(detailPlan)
    }
}

TourPlan 이라는 타입이 있습니다. 해당 타입을 통해 인스턴스를 생성하는 과정은 아래와 같습니다.

let longTour = TourPlan()
longTour.setTitle(title: "일본 여행")
longTour.setDays(days: 2)
longTour.bookLodging(lodging: "호텔")
longTour.addPlan(day: 1, detailPlan: "체크인")
longTour.addPlan(day: 1, detailPlan: "오사카 투어")
longTour.addPlan(day: 2, detailPlan: "체크아웃")
longTour.addPlan(day: 2, detailPlan: "교토 이동")

let shortTour = TourPlan()
shortTour.setTitle(title: "당일치기 기차 여행")
shortTour.setDays(days: 1)
shortTour.addPlan(day: 1, detailPlan: "9시 출발")

긴 여행이냐 짧은 여행이냐에 따라 필요한 구성이 달라지는 걸 확인할 수 있습니다.

init()convenience init() 을 통해 경우에 따른 생성자를 구현할 수도 있습니다. 관리해야 하는 생성자의 수가 많아지고 사용할 때 헷갈릴 수 있습니다.

패턴 적용

  • Builder
protocol TourBuilder {
    func setTitle(title: String) -> TourBuilder
    func setDays(days: Int) -> TourBuilder
    func bookLodging(lodging: String) -> TourBuilder
    func getTourPlan() -> TourPlan
}
  • ConcreteBuilder
class DefaultTourBuilder: TourBuilder {
    var title: String?
    var days: Int?
    var lodging: String?
    
    func setTitle(title: String) -> TourBuilder {
        self.title = title
        return self
    }
    
    func setDays(days: Int) -> TourBuilder {
        self.days = days
        return self
    }
    
    func bookLodging(lodging: String) -> TourBuilder {
        self.lodging = lodging
        return self
    }
    
    func getTourPlan() -> TourPlan {
        return TourPlan(title: self.title, days: self.days, lodging: self.lodging)
    }
}
  • Client
    Director를 사용하지 않는다면 아래 코드와 같이 사용할 수 있습니다.
let longTourBuilder = DefaultTourBuilder()
let japanTour = longTourBuilder.setTitle(title: "일본 여행")
                .setDays(days: 2)
                .bookLodging(lodging: "리조트")
                .getTourPlan()

각 메서드를 통해 필요한 값을 설정하고 getTourPlan() 메서드를 통해 인스턴스를 생성할 수 있습니다.

  • Director
class Director {
    let builder: TourBuilder
    
    init(builder: TourBuilder) {
        self.builder = builder
    }
    
    func getJapanTourPlan() -> TourPlan {
        return builder.setTitle(title: "일본 여행")
            .setDays(days: 2)
            .bookLodging(lodging: "리조트")
            .getTourPlan()
    }
}

let tourPlanDirector = Director(builder: DefaultTourBuilder())
let japanTourPlan = tourPlanDirector.getJapanTourPlan()

Director를 사용한다면 위에 코드처럼 작성할 수 있습니다. 또 한 위에 얘기한 대로 Director에 메서드를 정의해 자주 사용되는 인스턴스 구성을 미리 만들 수 있습니다.

장단점

  • 장점
  1. 입력받는 값에 따라 처리해야 하는 로직이 존재할 때 생성자 내부에서 해당 로직을 전부 처리하게 되면 복잡하지만 Builder 패턴을 사용하면 각각 입력에 따른 로직을 분리할 수 있다.
  2. 인스턴스 생성을 위한 프로세스를 제공한다.
  3. Director를 사용하면 복잡한 과정을 숨길 수 있다.
  • 단점
  1. 구조가 복잡하다.
  2. Client 쪽에서 생성해야 하는 인스턴스가 많아진다.(기존에는 TourPlan 하나만 생성하면 되지만 Director와 Builder를 미리 생성하고 TourPlan을 생성해야 한다.)

해당 글은 인프런의 코딩으로 학습하는 GoF 디자인 패턴 강의와 블로그를 참고해 작성했습니다.

참고 블로그
Refactoring.Guru

⭐️ 부족하거나 잘못된 부분이 있다면 댓글은 언제나 환영입니다!! ⭐️

0개의 댓글