복잡한 객체를 생성하는 과정을 추상화하고, 이를 구현한 다양한 클래스를 통해 객체를 생성하는 패턴.
우선 베라 맛보기 스푼처럼 간단한 예제를 보자.
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") 붕붕!")
}
}
let myCar = CarBuilder()
.withName("작고 소중한 소형차")
.withType(.compact)
.build()
myCar.run() // 작고 소중한 소형차 붕붕!
let carBuilder = CarBuilder()
.withName("반쯤 큰 중형차")
.withType(.midsize)
let yourCar = Car(builder: carBuilder)
yourCar.run() // 반쯤 큰 중형차 붕붕!
인터넷에서 찾아본 예제 중에 가장 단순하게 빌더 패턴을 설명해주는 예시 같아서 가져왔다. 하지만 이런 예시만으로는 빌더 패턴을 왜 사용하는지를 알기는 힘들어 보인다. 위 예제에서 아쉬운 점은 다음과 같다.
Car 클래스를 Usage에서 직접 초기화해서 쓰면 되는데 굳이 왜 빌더 클래스를 만든 이유가 무엇인가?
그럼 다음 예제를 통해 빌더 패턴이 어떤 상황에서 사용하기에 적합한지 알아보자.
캐쥬얼 스포츠 게임에서 사용할 자동차를 정의하는 클래스이다. 어린이 캐릭터에게는 미니카를, 어른 캐릭터에게는 포르쉐를 반환하는 빌더 클래스를 생성하였다.
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() {}
}
let childCar = CarBuilder().build(with: .child)
childCar.run() // 민희카 붕붕!
let adultCar = CarBuilder().build(with: .adult)
adultCar.run() // 포르쉐 붕붕!
앞서 살펴본 예제와 어떤 점에서 차이가 있을까? Car 프로토콜을 채택한 여러 자동차 클래스를 정의하고 빌더 클래스에서 특정 조건에 따라 다른 자동차 객체를 반환하였다.
이렇게 빌더 클래스를 구성했을 때의 장점은 나중에 다양한 Target이 추가되거나 다른 프로퍼티들이 추가되더라도, 자동차 클래스를 정의하고 빌더 클래스 내부의 build 함수에 case 하나만 추가해주면 된다는 것이다.