티스토리 뷰
Builder (빌더) - Creational Pattern (생성 패턴)
ios 빌더 패턴은 객체 생성, 데이터 그룹화, 명령어를 다룹니다.
ios 빌더 패턴은 많은 수의 설정(Configuration)을 가능하게 합니다. 이 덕분에 객체가 더 명확하고 직관적으로 되며, 테스트와 디버깅을 쉽게 할 수 있습니다.
빌더 패턴은 크게 3가지로 나누어진다.
- Director : 입력을 받아 Builder를 사용해 Product를 생성한다. 필수는 아니지만 입력이 많고 재사용이 필요한 경우 유용하게 사용할 수 있다.
- Builder : 입력을 받아 Product를 생성해주는 객체
- Product : 생성된 객체(클래스, 구조체)
When?
- 점층적 생성자(Telescopic Constructor) 를 피할 때
- 특정 객체에 대해 많은 View를 생성해야 할 때
- 복잡한 객체들을 합성해야 할 때
점층적 생성자 패턴(Telescopic Constructor Pattern)
클래스에서 여러 이니셜라이져를 가지는 패턴이다.
Swift에서는 프로퍼티에 초기값을 무조건 할당해주어야 하기 때문에 거의 사용되지는 않는다.
보통 빌더 패턴의 유용성을 비교하기 위해 점층적 생성자 패턴의 단점을 말한다.
import Foundation
// Models
enum DishCategory: Int {
case firstCourses, mainCourses, garnishes, drinks
}
struct Dish {
var name: String
var price: Float
}
struct OrderItem {
var dish: Dish
var count: Int
}
struct Order {
var firstCourses: [OrderItem] = []
var mainCourses: [OrderItem] = []
var garnishes: [OrderItem] = []
var drinks: [OrderItem] = []
var price: Float {
let items = firstCourses + mainCourses + garnishes + drinks
return items.reduce(Float(0), { $0 + $1.dish.price * Float($1.count) })
}
}
class OrderNormal {
private var order: Order?
init(firstCourse:OrderItem){ ... }
init(firstCourse:OrderItem, mainCourses: OrderItem){ ... }
init(firstCourse:OrderItem, mainCourses: OrderItem, garnishes: OrderItem){ ... }
...
}
이런 식으로 경우에 따라 이니셜라이져를 따로 작성해준다.
클래스의 프로퍼티가 많아질 경우 이니셜라이져의 수가 많아져 클래스가 복잡해진다.
ios에서는 복잡한 객체를 나누어 구현하기 위해 빌더 패턴을 사용한다
Code
import Foundation
// Models
enum DishCategory: Int {
case firstCourses, mainCourses, garnishes, drinks
}
struct Dish {
var name: String
var price: Float
}
struct OrderItem {
var dish: Dish
var count: Int
}
struct Order {
var firstCourses: [OrderItem] = []
var mainCourses: [OrderItem] = []
var garnishes: [OrderItem] = []
var drinks: [OrderItem] = []
var price: Float {
let items = firstCourses + mainCourses + garnishes + drinks
return items.reduce(Float(0), { $0 + $1.dish.price * Float($1.count) })
}
}
모델은 위 예제와 동일하다.
// Builder
class OrderBuilder {
private var order: Order?
func reset() {
order = Order()
}
func setFirstCourse(_ dish: Dish) {
set(dish, at: order?.firstCourses, withCategory: .firstCourses)
}
func setMainCourse(_ dish: Dish) {
set(dish, at: order?.mainCourses, withCategory: .mainCourses)
}
func setGarnish(_ dish: Dish) {
set(dish, at: order?.garnishes, withCategory: .garnishes)
}
func setDrink(_ dish: Dish) {
set(dish, at: order?.drinks, withCategory: .drinks)
}
func getResult() -> Order? {
return order ?? nil
}
private func set(_ dish: Dish, at orderCategory: [OrderItem]?, withCategory dishCategory: DishCategory) {
guard let orderCategory = orderCategory else {
return
}
var item: OrderItem! = orderCategory.filter( { $0.dish.name == dish.name } ).first
guard item == nil else {
item.count += 1
return
}
item = OrderItem(dish: dish, count: 1)
switch dishCategory {
case .firstCourses:
order?.firstCourses.append(item)
case .mainCourses:
order?.mainCourses.append(item)
case .garnishes:
order?.garnishes.append(item)
case .drinks:
order?.drinks.append(item)
}
}
}
빌더 패턴을 사용해 주문을 받는 코드이다.
이니셜라이져를 사용하는 것이 아니라 set함수를 구현한 후, 이를 기반으로 setFristCourse, setMainCourse 등 DishCategory에 따라 메소드를 구현해주었다.
// Usage
let steak = Dish(name: "Steak", price: 2.30)
let chips = Dish(name: "Chips", price: 1.20)
let coffee = Dish(name: "Coffee", price: 0.80)
let builder = OrderBuilder()
builder.reset()
builder.setMainCourse(steak)
builder.setGarnish(chips)
builder.setDrink(coffee)
let order = builder.getResult()
order?.price
// Result:
// 4.30
빌더의 setMaincourse 메소드 등을 사용해 더 효율적으로 작성할 수 있다.
만약 빌더 패턴을 사용하지 않고 점층적 생성자 패턴을 사용한다면?
OrderNormal(mainCourse: Steak, garnish: chips, drink: coffee)
이런 식으로 작성해야 할 것이다.
지금은 메뉴가 3개지만 메뉴가 10개가 넘어간다면???
OrderNormal 객체를 생성하기 위해 엄청나게 긴 코드를 작성해야 한다.
그에 따라 가독성도 떨어지고 OrderNormal 객체 내부도 각각의 경우에 따라 이니셜라이져를 작성해주어야하기 때문에 매우 복잡해질 것이다.
Director
이번에는 디렉터를 작성해보겠다.
class OrderDirector {
let builder = OrderBuilder()
func setCourseA() -> OrderBuilder{
builder.reset()
builder.setMainCourse(Dish(name: "Steak", price: 2.30))
builder.setGarnish(Dish(name: "Chips", price: 1.20))
builder.setDrink(Dish(name: "Coffee", price: 0.80))
return builder
}
func setCourseB() -> OrderBuilder{
builder.reset()
builder.setMainCourse(Dish(name: "Salmon", price: 2.10))
builder.setGarnish(Dish(name: "Photato", price: 0.80))
builder.setDrink(Dish(name: "Coke", price: 0.30))
return builder
}
}
코스 A와 코스 B를 만들었다.
이제 코스요리를 주문하면 setCourse 메소드를 호출하면 된다.
let Director = OrderDirector()
let orderCourseA = dircetor.setCoureA()
let order = orderCourse.getResult()
order?.price
// Result:
// 4.30
코드가 훨씬 짧아졌음을 확인할 수 있다.
앞서 말했듯이 Direcotr는 빌더 패턴에서 필수가 아니다.
재사용을 할 때 사용하면 코드를 더 효율적이고 가독성있게 작성할 수 있다.
Result
Builder 패턴의 장점
GOF의 디자인 패턴 책에 의하면 Builder 패턴의 장점은 3가지이다.
1. 표현을 다양하게 변경할 수 있다.
표현을 다양하게 변경할 수 있다는 것이 애매모호하긴 한데 추가와 수정이 간단하다는 뜻으로 해석했다.
2. 생성과 표현에 필요한 코드를 분리한다. (= 모델과 set 함수가 분리되어 있다)
3. 복합 객체를 생성하는 절차를 더 세밀하게 나눌 수 있다.
디저트를 추가할 일이 있으면 DishCategory, Order에 추가해주고 OrderBuilder에 set함수만 추가해주면된다.
enum DishCategory: Int {
case firstCourses, mainCourses, garnishes, drinks, dessert
}
struct Order {
var firstCourses: [OrderItem] = []
var mainCourses: [OrderItem] = []
var garnishes: [OrderItem] = []
var drinks: [OrderItem] = []
var dessert: [OrderItem] = []
var price: Float {
let items = firstCourses + mainCourses + garnishes + drinks
return items.reduce(Float(0), { $0 + $1.dish.price * Float($1.count) })
}
}
class OrderBuilder {
private var order: Order?
func reset() {
order = Order()
}
func setDessert(_ dish: Dish) {
set(dish, at: order?.dessert, withCategory: .dessert)
}
...
}
복합 객체를 생성하는 절차를 세밀하게 나누는 것은 위에서 OrderBuilder 와 OrderNormal의 차이로 설명을 했다.
추가적으로 책에 나온 것 뿐만 아니라 구글에 나온 장점을 보면 아래와 같다.
- 하나의 생성자(=이니셜라이져)만을 재사용해 제품을 만들 수 있다
- 단일 책임 원칙을 지킬 수 있다
단점
1. 여러 개의 새로운 클래스를 생성할 때 코드의 전체적인 복잡도가 증가한다
예제에서는 하나의 order만 다루었지만 order가 여러개 되면 set 메소드를 여러 번 작성해주어야 하니 아무래도 코드가 길어질 수 밖에 없다.
Swift에서 많이 사용되는 디자인 패턴인 Builder 패턴에 대해 공부해보았다. 😁
참고
GOF의 디자인 패턴 / 에릭 감마 외
https://aglowiditsolutions.com/blog/top-swift-design-patterns/
https://refactoring.guru/design-patterns/builder
'Swift > Design Pattern' 카테고리의 다른 글
[Swift Design Pattern 05] Decorator (데코레이터) (1) | 2022.03.12 |
---|---|
[Swift Design Pattern 04] Facade (퍼사드) (0) | 2022.03.08 |
[Swift Design Pattern 03] Factory Method (0) | 2022.03.05 |
[Swift Design Pattern 02] Singleton (0) | 2022.03.04 |
[Swift Design Pattern 0] Swift에서 많이 쓰이는 11가지 패턴 (0) | 2022.02.27 |
- Total
- Today
- Yesterday
- 날씨어플
- 애플
- Swift문법
- 앱개발
- 코딩 테스트
- Swift 서버
- 프로그래머스
- Swift공식문서
- swiftUI 기초
- todo앱
- 부스트캠프iOS
- vapor
- 부스트캠프7기
- 개발
- 디자인 패턴
- 코딩테스트
- 필독서
- Swift DocC
- 책리뷰
- Swift
- 코딩
- Swift 디자인 패턴
- TODO
- 부스트캠프
- SwiftUI
- UX
- 책
- Combine
- ios
- 책후기
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |