* 예제 코드 및 설명에 필요한 개념들이 Swift를 기준으로 작성된 글입니다.
Creational Patterns의 세 번째 Pattern은 Builder 입니다.
1. 설계 목적
복잡한 객체들을 단계별로 생성할 수 있는 패턴입니다. 같은 구조화 코드를 이용해서 객체의 다양한 타입과 표현을 생성할 수 있습니다.

2. 문제 상황
많은 필드와 Nested Objects를 단계별로 초기화해야하는 객체가 있다고 가정합시다.

예시로는 House 객체가 있습니다. 여기서 단순한 집이 아닌 다양한 형태의 집이 필요하다면 어떻게 해야할까요?
사실 아래 이미지처럼 House
객체에 다양한 Property들을 추가하여 초기화 시에 매개변수로 전달하거나, 자식 클래스를 정의하는 방법으로 해결할 수 있습니다.

하지만 예상 가능하듯이 House
내부에는 불필요한 Property가 가득해질 것입니다.
3. 해결책
Builder
이를 해결하기위해 Builder를 정의합니다. Builder는 객체 생성 코드를 가지고 있습니다.
House의 Walls
, Door
등을 buildWalls
, buildDoor
등의 단계들을 나누고 Builder 내부에서 필요한 객체들이 생성됩니다.

이를 코드로 나타내면 다음과 같습니다.
class House {
}
protocol Builder {
func reset()
func buildWalls(count: Int)
func buildDoors(count: Int)
func buildWindows(count: Int)
func buildRoof(size: Int)
func buildGarden(size: Int)
func buildGarage(_ isBuild: Bool)
func buildFancyStatues(_ isBuild: Bool)
func buildSwimmingPool(_ isBuild: Bool)
func getResult() -> House
}
class HouseBuilder: Builder {
private var house: House
init() {
self.reset()
}
func reset() {
self.house = House()
}
func buildWalls(count: Int) {
print("Build \(count) Walls")
}
func buildDoors(count: Int) {
print("Build \(count) Doors")
}
func buildWindows(count: Int) {
print("Build\(count) Windows")
}
func buildRoof(size: Int) {
print("Build \(size) Size Roof")
}
func buildGarden(size: Int) {
print("Build \(size) Size Garden")
}
func buildGarage(_ isBuild: Bool) {
if isBuild {
print("Build Garage")
}
}
func buildFancyStatues(_ isBuild: Bool) {
if isBuild {
print("Build FancyStatues")
}
}
func buildSwimmingPool(_ isBuild: Bool){
if isBuild {
print("Build buildSwimmingPool")
}
}
func getResult() -> House {
let house = self.house
self.reset()
return house
}
}
중요한 점은 완전한 House
객체가 생성되기 전까지 다른 객체들의 접근을 허용하지 않는다는 것입니다.
Director
선택적으로 Director를 정의할 수 있습니다.
Builder가 객체 생성에 대한 인터페이스를 제공한다면, Director는 객체 생성 단계(초기화 순서)를 제공합니다.
Client 코드에서 Builder만을 사용하여 특정 순서로 호출할 수 있기 때문에 Director는 선택적인 요소입니다.
Director 사용의 장점은 Client 코드에서 객체 생성에 대한 세부 정보(단계)를 완전히 숨길 수 있다는 점입니다.
그렇게 되면 Client는 Builder로부터 생성된 객체를 반환받기만 하면 됩니다.
Director는 아래와 같이 설계됩니다.
class Director {
func constructDefaultHouse(builder: Builder) {
builder.reset()
builder.buildWalls(count: 4)
builder.buildDoors(count: 2)
builder.buildWindows(count: 3)
builder.buildRoof(size: 16)
builder.buildGarden(size: 5)
builder.buildGarage(false)
builder.buildFancyStatues(false)
builder.buildSwimmingPool(false)
}
func constructHouseWithSwimmingPool(builder: Builder) {
builder.reset()
builder.buildWalls(count: 4)
builder.buildDoors(count: 2)
builder.buildWindows(count: 3)
builder.buildRoof(size: 16)
builder.buildGarden(size: 5)
builder.buildGarage(false)
builder.buildFancyStatues(false)
builder.buildSwimmingPool(true)
}
func constructHouseWithFancyStatuesAndGarage(builder: Builder) {
builder.reset()
builder.buildWalls(count: 8)
builder.buildDoors(count: 4)
builder.buildWindows(count: 5)
builder.buildRoof(size: 24)
builder.buildGarden(size: 5)
builder.buildGarage(true)
builder.buildFancyStatues(true)
builder.buildSwimmingPool(false)
}
}
4. 구조

5. 예제 코드의 사용
클라이언트에서는 어떻게 사용될까요?
class Application {
func makeHouse() {
let director = Director()
let houseBuilder = HouseBuilder()
director.constructDefaultHouse(builder: houseBuilder)
let defaultHouse = houseBuilder.getResult()
director.constructHouseWithFancyStatuesAndGarage(builder: houseBuilder)
let luxuryHouse = houseBuilder.getResult()
}
}
복잡한 생성자가 사라지고 원하는 형태의 House
를 반환받을 수 있게 되었습니다.
6. 장단점
Telescoping Constructor(점층적 생성자)를 제거할 수 있습니다.
Telescoping Constructor란 다음과 같은 경우를 이야기합니다.
class Pizza {
init(size: Int) { ... }
init(size: Int, cheese: Bool) { ... }
init(size: Int, cheese: Bool, pepperoni: Bool) { ... }
// ...
아래 코드처럼 객체 생성 프로세스를 재활용 할 수도 있습니다.
class Director {
func constructDefaultHouse(builder: Builder) {
builder.reset()
...
}
func constructHouseWithSwimmingPool(builder: Builder) {
constructDefaultHouse(builder: builder)
builder.buildGarage(false)
builder.buildFancyStatues(false)
builder.buildSwimmingPool(true)
}
...
}
하지만 하나의 패턴에서 여러 가지 객체들이 생성되기 때문에 코드의 복잡성이 증가합니다.
7. 실제 사용 사례
그렇다면 Builder 패턴을 iOS 앱 설계에 있어 어디에 적용시킬 수 있을까요?
UIAlertViewController
에 적용시켜보면 어떨까요?
protocol AlertBuilder {
func reset(style: UIAlertController.Style)
func setTitle(_ text: String)
func setMassage(_ text: String)
func setDefaultButton(_ isBuild: Bool)
func setCancelButton(_ isBuild: Bool)
func build() -> UIAlertController
}
class AlertViewBuilder: AlertBuilder {
private var alertViewController: UIAlertController
init() {
self.reset()
}
func reset(style: UIAlertController.Style = .alert) {
self.alertViewController = UIAlertController(
title: nil,
message: nil,
preferredStyle: style
)
}
func setTitle(_ text: String) {
alertViewController.title = text
}
func setMassage(_ text: String) {
alertViewController.message = text
}
func setDefaultButton(_ isBuild: Bool) {
if isBuild {
let alertAction = UIAlertAction(title: "OK", style: .default)
alertViewController.addAction(alertAction)
}
}
func setCancelButton(_ isBuild: Bool) {
if isBuild {
let alertAction = UIAlertAction(title: "Cancel", style: .cancel)
alertViewController.addAction(alertAction)
}
}
func build() -> UIAlertController {
let alertViewController = self.alertViewController
self.reset()
return alertViewController
}
}
AlertBuilder
와 AlertViewBuilder
를 정의하고, 아래와 같이 AlertViewDirector
를 정의합니다.
class AlertViewDirector {
func constructDefaultAlert(builder: AlertBuilder) {
builder.reset(style: .alert)
builder.setTitle("Builder")
builder.setMassage("This is the Builder Pattern.")
builder.setDefaultButton(true)
}
func constructDefaultAlertWithCancel(builder: AlertBuilder) {
constructDefaultAlert(builder: builder)
builder.setMassage("Is this the Builder Pattern?")
builder.setCancelButton(true)
}
func constructActionSheetAlert(builder: AlertBuilder) {
builder.reset(style: .actionSheet)
builder.setTitle("Builder")
builder.setMassage("Action Sheet Alert")
builder.setDefaultButton(true)
}
}
그러면 Client 에는 Alert을 더 쉽게 생성할 수 있습니다.
class SomeUIViewController: UIViewController {
func showAlert() {
let director = AlertViewDirector()
let alertBuilder = AlertViewBuilder()
director.constructDefaultAlertWithCancel(builder: alertBuilder)
let alertViewController = alertBuilder.build()
self.present(alertViewController, animated: true)
}
}
이렇게 필요한 AlertViewController
의 종류를 잘 설계해놓으면 매우 편리할 듯합니다.
8. Conclusion
Builder 패턴은 어렵지 않게 프로젝트에 적용시킬 수 있는 패턴이라고 생각합니다.
예시로 작성된 AlertViewController
뿐만 아니라 URLRequest
, Animation
등에 적용해보면 어떨까 합니다.
하지만 항상 필요성과 사이드 이펙트를 고려해서 적용해야함을 잊지맙시다.
Reference
https://refactoring.guru/design-patterns/abstract-factory
Abstract Factory
Solution The first thing the Abstract Factory pattern suggests is to explicitly declare interfaces for each distinct product of the product family (e.g., chair, sofa or coffee table). Then you can make all variants of products follow those interfaces. For
refactoring.guru
'Design Pattern > GoF - Creational Patterns' 카테고리의 다른 글
[GoF Design Patterns] Singleton (0) | 2024.10.18 |
---|---|
[GoF Design Patterns] Prototype (0) | 2024.10.01 |
[GoF Design Patterns] Abstract Factory - 실제 사용 예제 (1) | 2024.09.26 |
[GoF Design Patterns] Abstract Factory (0) | 2024.09.26 |
[GoF Design Patterns] Factory Method (0) | 2024.09.24 |