728x90
Builder Pattern
복잡한 Object 에 대해 initializer에서 필요한 모든 input을 입력하지 않고 단계별로 input을 줄 수 있게 한다.
빌더 패턴은 3가지 부분으로 나눠진다.
- director: input 을 accept 하고 builder 와 coordinate 한다.
대부분 view controller에 의해 사용되는 view controller 나 helper class 가 담당한다. - product: 생성하기 힘든 복잡한 객체이다. struct 나 class 가 될 수 있고 대부분 model 역할이다. 그런데 use case 에 따라 아무 타입이나 다 될 수 있다.
- builder: 단계별로 input을 받고 product 의 생성을 처리한다. 대부분 class 이고 refernce 에 의해 재사용될 수 있다.
Builder Pattern - 언제 사용해야 할까
- 복잡한 객체를 단계별로 만들어 나갈때 사용한다.
- 이 패턴은 특히 여러개의 입력값 (multiple input) 이 있을때 사용된다.
- builder 는 어떻게 input들이 객체 생성을 위해 사용되는 지를 추상화 하고 input을 director 가 제공하는 순서에 관계없이 받는다(accept)
- 예를 들어 Builder Pattern 으로 "hamburger builder"를 만들 수 있다.
- product: 햄버거 model (고기나 야채 치즈 등 여러가지 선택할 수 있는 복잡한 모델)
- director: 종업원 객체 (어떻게 햄버거를 만드는지 아는 객체) 또는 사용자로 부터 input 을 받는 view controller
Builder Pattern 예제
Product 코드
- meat, sauce, toppings 프로퍼티를 가진 Hamburger를 정의한다.
햄버거가 한번 만들어 지면 그 구성을 바꿀 수 없으므로 let 으로 프로퍼티를 선언한다.
CustomStringConvertible 을 준수하여 나중에 어떤 햄버거인지 print 하게 한다. - Meat 를 enum 으로 선언하여 하나의 종류의 고기만 선택할 수 있게 한다.
- Sauces 는 OptionalSet 으로 선언하여 여러가지 조합으로 선택할 수 있게 한다.
- 같은 이유로 Toppings 도 OptionalSet 으로 선언한다.
import Foundation
//Product
public struct Hamburger {
public let meat: Meat
public let sauce: Sauces
public let toppings: Toppings
}
extension Hamburger: CustomStringConvertible {
public var description: String{
return meat.rawValue + " burger"
}
}
public enum Meat: String {
case beef
case chicken
case kitten
case tofu
}
public struct Sauces: OptionSet {
public static let mayonnaise = Sauces(rawValue: 1 << 0)
public static let mustard = Sauces(rawValue: 1 << 1)
public static let ketchup = Sauces(rawValue: 1 << 2)
public static let secret = Sauces(rawValue: 1 << 3)
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
}
public struct Toppings: OptionSet {
public static let cheese = Toppings(rawValue: 1 << 0)
public static let lettuce = Toppings(rawValue: 1 << 1)
public static let pickles = Toppings(rawValue: 1 << 2)
public static let tomatoes = Toppings(rawValue: 1 << 3)
public let rawValue: Int
public init(rawValue: Int) {
self.rawValue = rawValue
}
}
Builder 코드
- meat, sauces, toppings를 햄버거 input 에 맞게 선언한다. 그리고 product 코드와 다르게 var 로 선언한다. 그리고 각각 private (set)으로 HamburgerBuilder 만 직접적으로 set 할 수 있게 만든다.
- 모든 property 를 private(set)으로 정했기 때문에 public method가 필요하다. addSauces(_:), removeSauces(_:), addToppings(_:), removeToppings(_:), setMeat(_:) 의 public method 를 정의한다.
- 선택된 대로 Hamburger를 만들 수 있는 build() 를 정의한다.
public class HamburgerBuilder {
public private(set) var meat: Meat = .beef
public private(set) var souces: Sauces = []
public private(set) var toppings: Toppings = []
public func addSauces(_ sauce: Sauces) {
sauces.insert(sauce)
}
public func removeSauces(_ sauce: Sauces) {
sauces.remove(sauce)
}
public func addToppings(_ topping: Toppings) {
toppings.inert(topping)
}
public func removeToppings(_ topping: Toppings) {
toppings.remove(topping)
}
public func setMeat(_ meat: Meat) {
self.meat = meat
}
public func build() -> Hamburger {
return Hamburger(meat: meat, sauce : sauces, toppings: toppings)
}
}
위 코드에서 private(set)을 통해 consumer 가 강제로 public setter 메소드를 쓰게 했다.
이 방법은 builder 가 값을 정하기 전에 validation을 할 수 있게 한다.
예를들어 meat 의 종류중 품절된게 있는 경우
private var soldOutMeats: [Meat] = [.kitten]
public enum Error: Swift.Error {
case soldOut
}
public func setMeat(_ meat: Meat) throws {
guard isAvailable(meat) else { throw Error.soldOut }
self.meat = meat
}
public func isAvailable(_ meat: Meat) -> Bool {
return !soldOutMeats.contains(meat)
}
기존에 setMeat 메소드에 isAvailable 을 통해 Validation을 할 수 있다.
Director 코드
public class Employee {
public func createCombo1() throws -> Hamburger {
let builder = HamburgerBuilder()
try builder.setMeat(.beef)
builder.addSauces(.secret)
builder.addToppings([.lettuce, .tomatoes, .pickkles])
return builder.build()
}
public func createKittenSpecial() throws -> Hamburger {
let builder = HamburgerBuilder()
try builder.setMeat(.kitten)
builder.addSuaces(.mustard)
builder.addToppings([.lettuce, .tomatoes])
return builder.build()
}
}
Employee 는 위 두 함수처럼 두종류의 버거를 만들줄 안다.
코드 실행해서 결과보기
let burgerFlipper = Employee()
if let combo1 = try? burgerFlipper.createCombo1() {
print("Nom nom " + combo1.description)
}
if let kittenBurger = try?
burgerFlipper.createKittenSpeial() {
print("Nom nom nom " + kittenBurger.description)
} else {
print("no kitten burger here")
}
combo1의 경우 성공적으로 object 가 만들어지는데 kittenBurger 의 경우 Kitten 이 품절이므로 no kitten burger here 가 출력된다.
Builder Pattern 사용시 주의할 점
- product 가 여러개의 input을 필요로 하지 않거나 단계별로 만들어져야 되는 object 가 아니라면 이 패턴을 쓸 가치가 없다.
- 위의 경우 그냥 initializer 를 통해 product 를 만드는게 좋다.
728x90
'iOS > Design Pattern' 카테고리의 다른 글
Design Pattern - Factory Pattern (0) | 2020.08.25 |
---|---|
Design Pattern - Model View ViewModel Pattern (2) | 2020.08.24 |
Design Pattern - Observer Pattern (0) | 2020.08.09 |
Design Pattern - Memento Pattern (0) | 2020.08.09 |
Design Pattern - Singleton Pattern (0) | 2020.08.09 |
댓글