[Swift/디자인패턴] Builder Pattern

frogKing·2023년 3월 12일
0

디자인패턴

목록 보기
1/6
post-thumbnail

Builder Pattern

복잡한 객체를 생성하는 과정을 추상화하고, 이를 구현한 다양한 클래스를 통해 객체를 생성하는 패턴.

예제

우선 베라 맛보기 스푼처럼 간단한 예제를 보자.

Example

Car라는 클래스를 생성하고 Car 객체를 생성하는 CarBuilder 클래스를 구현하였다.

enum CarType {
    case compact
    case midsize
}

final class CarBuilder {
    private var car = Car()
    
    func withName(_ name: String) -> Self {
        car.name = name
        return self
    }
    
    func withType(_ type: CarType) -> Self {
        car.type = type
        return self
    }
    
    func build() -> Car {
        return car
    }
}

class Car {
    var name: String?
    var type: CarType?
    
    init(name: String?, type: CarType?) {
				self.name = name
				self.type = type
		}
    
    init(builder: CarBuilder) {
        let car = builder.build()
        self.name = car.name
        self.type = car.type
    }
    
    func run() {
        print("\(name ?? "no name") 붕붕!")
    }
}

Usage

let myCar = CarBuilder()
    .withName("작고 소중한 소형차")
    .withType(.compact)
    .build()

myCar.run() // 작고 소중한 소형차 붕붕!

let carBuilder = CarBuilder()
    .withName("반쯤 큰 중형차")
    .withType(.midsize)

let yourCar = Car(builder: carBuilder)
yourCar.run() // 반쯤 큰 중형차 붕붕!

인터넷에서 찾아본 예제 중에 가장 단순하게 빌더 패턴을 설명해주는 예시 같아서 가져왔다. 하지만 이런 예시만으로는 빌더 패턴을 왜 사용하는지를 알기는 힘들어 보인다. 위 예제에서 아쉬운 점은 다음과 같다.

Car 클래스를 Usage에서 직접 초기화해서 쓰면 되는데 굳이 왜 빌더 클래스를 만든 이유가 무엇인가?

그럼 다음 예제를 통해 빌더 패턴이 어떤 상황에서 사용하기에 적합한지 알아보자.

Example

캐쥬얼 스포츠 게임에서 사용할 자동차를 정의하는 클래스이다. 어린이 캐릭터에게는 미니카를, 어른 캐릭터에게는 포르쉐를 반환하는 빌더 클래스를 생성하였다.

enum Target {
    case adult
    case child
}

final class CarBuilder {
    func build(with target: Target) -> Car {
        switch target {
        case .adult:
            return Porsche()
        case .child:
            return MinheeCar()
        }
    }
}

protocol Car {
    var name: String? { get set }
    var type: CarType?  { get set }
    
    func run()
}

extension Car {
    func run() {
        print("\(self) 붕붕!")
    }
}

class MinheeCar: Car, CustomStringConvertible {
    var description: String = "민희카"
    
    var name: String?
    var type: CarType?
    
    init() {}
}

class Porsche: Car, CustomStringConvertible {
    var description: String = "포르쉐"
    
    var name: String?
    var type: CarType?
    
    init() {}
}

Usage

let childCar = CarBuilder().build(with: .child)
childCar.run() // 민희카 붕붕!

let adultCar = CarBuilder().build(with: .adult)
adultCar.run() // 포르쉐 붕붕!

앞서 살펴본 예제와 어떤 점에서 차이가 있을까? Car 프로토콜을 채택한 여러 자동차 클래스를 정의하고 빌더 클래스에서 특정 조건에 따라 다른 자동차 객체를 반환하였다.

이렇게 빌더 클래스를 구성했을 때의 장점은 나중에 다양한 Target이 추가되거나 다른 프로퍼티들이 추가되더라도, 자동차 클래스를 정의하고 빌더 클래스 내부의 build 함수에 case 하나만 추가해주면 된다는 것이다.

결론

  • 특정 조건에 따라 다른 객체를 생성해줘야 하는 상황에서 빌더 패턴을 사용하면 유지보수 및 코드의 재사용성 측면에서 이점이 생긴다.
  • 빌더 패턴 자체는 단순하다. 조건에 맞는 객체를 생성한다는 책임을 가진다. 이 말은 다양한 조건 하에 다양한 객체를 생성할 수 있다는 것을 의미한다.
  • 어떤 상황에 적절하게 사용될 수 있을지 고민해보고 현업에서 꼭 필요한 상황에 적용해보면서 내 것으로 만들자.
profile
내가 이걸 알고 있다고 말할 수 있을까

0개의 댓글