本文大部分内容翻译至《Pro Design Pattern In Swift》By Adam Freeman,一些地方做了些许修改,并将代码升级到了Swift2.0,翻译不当之处望多包涵。
建造者模式(The Builder Pattern)
建造者模式用来将对象的配置从创建中分离出来。请求组件有配置数据并将它传递给中间人-建造者-它负责创建代表组件的对象。建造者模式它可以将复杂对象的建造过程抽象出来(抽象类别),使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
示例工程
OS X Command Line Tool工程:
Food.swift
class Burger {
let customerName:String
let veggieProduct:Bool
let patties:Int
let pickles:Bool
let mayo:Bool
let ketchup:Bool
let lettuce:Bool
let cook:Cooked
enum Cooked : String {
case RARE = "Rare"
case NORMAL = "Normal"
case WELLDONE = "Well Done"
}
init(name:String, veggie:Bool, patties:Int, pickles:Bool, mayo:Bool,
ketchup:Bool, lettuce:Bool, cook:Cooked) {
self.customerName = name
self.veggieProduct = veggie
self.patties = patties
self.pickles = pickles
self.mayo = mayo
self.ketchup = ketchup
self.lettuce = lettuce
self.cook = cook
}
func printDescription() {
print("Name \(self.customerName)")
print("Veggie: \(self.veggieProduct)")
print("Patties: \(self.patties)")
print("Pickles: \(self.pickles)")
print("Mayo: \(self.mayo)")
print("Ketchup: \(self.ketchup)")
print("Lettuce: \(self.lettuce)")
print("Cook: \(self.cook.rawValue)")
}
}
接下来main.swift
main.swift
let order = Burger(name: "Joe", veggie: false, patties: 2, pickles: true,
mayo: true, ketchup: true, lettuce: true, cook: Burger.Cooked.NORMAL)
order.printDescription()
运行程序:
Name Joe
Veggie: false
Patties: 2
Pickles: true
Mayo: true
Ketchup: true
Lettuce: true
Cook: Normal
理解建造者模式解决的问题
当一个对象需要大量的配置数据的时候,建造者模式出现了。在上面的例子中,Burger类的初始化方法就要求每一个方面的配置数据。下面的是虚构的餐馆预定汉堡包过程:
- 服务员问顾客的姓名
- 服务员问顾客是否需要素食
- 服务员问顾客是否要定做汉堡包
- 服务员问顾客是否要升级买一个额外的肉饼
上面只有4个步骤,但却抛出了一些问题。其实我们在创建Burger对象的时候是这样的:
main.swift
// Step 1 - Ask for name
let name = "Joe"
// Step 2 - Is veggie meal required?
let veggie = false
// Step 3 - Customize burger?
let pickles = true
let mayo = false
let ketchup = true
let lettuce = true
// Step 4 - Buy additional patty?
let patties = 2
let cooked = Burger.Cooked.NORMAL
let order = Burger(name: name, veggie: veggie, patties: patties, pickles: pickles, mayo: mayo, ketchup: ketchup, lettuce: lettuce, cook: cooked)
order.printDescription()
Burger类的初始化方法要求请求组件知道默认的值当顾客不想改变Burger的配置的时候。每一个请求组件都必须知道这个事情,那就意味着如果改变一个默认的值,那么就必须改变每一个请求组件。
理解建造者模式
建造者模式解决这个问题通过引出一个中间人-建造者-在请求组件和需要创建的对象之间。
实现建造者模式
1. 定义创建者类
首先,就是创建建造者类,建造者类提供了Burger类参数的默认值并且允许请求组件去改变这些值。
Builder.swift
class BurgerBuilder {
private var veggie = false
private var pickles = true
private var mayo = true
private var ketchup = true
private var lettuce = true
private var cooked = Burger.Cooked.NORMAL
private var patties = 2
func setVeggie(choice: Bool) { self.veggie = choice }
func setPickles(choice: Bool) { self.pickles = choice }
func setMayo(choice: Bool) { self.mayo = choice }
func setKetchup(choice: Bool) { self.ketchup = choice }
func setLettuce(choice: Bool) { self.lettuce = choice }
func setCooked(choice: Burger.Cooked) { self.cooked = choice }
func addPatty(choice: Bool) { self.patties = choice ? 3 : 2 }
func buildObject() -> Burger{
return Burger(name: name, veggie: veggie, patties: patties, pickles: pickles, mayo: mayo, ketchup: ketchup, lettuce: lettuce, cook: cooked)
}
}
2. 使用建造者
main.swift
var builder = BurgerBuilder()
// Step 1 - Ask for name
let name = "Joe"
// Step 2 - Is veggie meal required?
builder.setVeggie(false)
// Step 3 - Customize burger?
builder.setMayo(false)
builder.setCooked(Burger.Cooked.WELLDONE)
// Step 4 - Buy additional patty?
builder.addPatty(false)
let order = builder.buildObject(name)
order.printDescription()
运行程序,输出:
Name Joe
Veggie: false
Patties: 2
Pickles: true
Mayo: false
Ketchup: true
Lettuce: true
Cook: Well Done
建造者模式的变形
你可以将建造者模式和其他模式结合起来,一般是和工厂方法模式和抽象工厂模式。用得最多的就是定义复数的建造者并且让它们都实现工厂方法模式。
Builder.swift
enum Burgers {
case STANDARD
case BIGBURGER
case SUPERVEGGIE
}
class BurgerBuilder {
private var veggie = false
private var pickles = true
private var mayo = true
private var ketchup = true
private var lettuce = true
private var cooked = Burger.Cooked.NORMAL
private var patties = 2
private var bacon = true
private init() {
// do nothing
}
func setVeggie(choice: Bool) {
self.veggie = choice
if (choice) {
self.bacon = false
}
}
func setPickles(choice: Bool) { self.pickles = choice }
func setMayo(choice: Bool) { self.mayo = choice }
func setKetchup(choice: Bool) { self.ketchup = choice }
func setLettuce(choice: Bool) { self.lettuce = choice }
func setCooked(choice: Burger.Cooked) { self.cooked = choice }
func addPatty(choice: Bool) { self.patties = choice ? 3 : 2 }
func setBacon(choice: Bool) { self.bacon = choice }
func buildObject(name:String) -> Burger{
return Burger(name: name, veggie: veggie, patties: patties, pickles: pickles, mayo: mayo, ketchup: ketchup, lettuce: lettuce, cook: cooked,bacon: bacon)
}
class func getBuilder(burgerType:Burgers) -> BurgerBuilder {
var builder:BurgerBuilder
switch (burgerType) {
case .BIGBURGER: builder = BigBurgerBuilder()
case .SUPERVEGGIE: builder = SuperVeggieBurgerBuilder()
case .STANDARD: builder = BurgerBuilder()
}
return builder
}
}
class BigBurgerBuilder : BurgerBuilder {
private override init() {
super.init()
self.patties = 4
self.bacon = false
}
override func addPatty(choice: Bool) {
fatalError("Cannot add patty to Big Burger")
}
}
class SuperVeggieBurgerBuilder : BurgerBuilder {
private override init() {
super.init()
self.veggie = true
self.bacon = false
}
override func setVeggie(choice: Bool) {
// do nothing - always veggie
}
override func setBacon(choice: Bool) {
fatalError("Cannot add bacon to this burger")
}
}
接着是:
main.swift
// Step 1 - Ask for name
let name = "Joe"
// Step 2 - Select a Product
let builder = BurgerBuilder.getBuilder(Burgers.BIGBURGER)
// Step 3 - Customize burger?
builder.setMayo(false)
builder.setCooked(Burger.Cooked.WELLDONE)
let order = builder.buildObject(name)
order.printDescription()
运行程序,输出:
Name Joe
Veggie: false
Patties: 4
Pickles: true
Mayo: false
Ketchup: true
Lettuce: true
Cook: Well Done
Cocoa中的建造者模式
Cocoa中最常用的使用的建造者模式的类就是 NSDateComponents类,NSDateComponents类允许请求组件设置值来创建 NSDate类。请看下面例子:
import Foundation
var builder = NSDateComponents()
builder.hour = 10
builder.day = 6
builder.month = 9
builder.year = 1940
builder.calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)
var date = builder.date
print(date!)
运行程序,输出:
1940-09-06 01:00:00 +0000