本文大部分内容翻译至《Pro Design Pattern In Swift》By Adam Freeman,一些地方做了些许修改,并将代码升级到了Swift2.0,翻译不当之处望多包涵。
工厂方法模式(The Factory Method Pattern)
工厂方法模式是一种实现了“工厂”概念的面向对象设计模式。就像其他创建型模式一样,它也是处理在不指定对象具体类型的情况下创建对象的问题。工厂方法模式的实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。
准备示例工程
OS X Command Line Tool 工程:
RentalCar.swift
protocol RentalCar {
var name:String { get }
var passengers:Int { get }
var pricePerDay:Float { get }
}
class Compact : RentalCar {
var name = "VW Golf"
var passengers = 3
var pricePerDay:Float = 20
}
class Sports : RentalCar {
var name = "Porsche Boxter"
var passengers = 1
var pricePerDay:Float = 100
}
class SUV : RentalCar {
var name = "Cadillac Escalade"
var passengers = 8
var pricePerDay:Float = 75
}
可以看见有一个RentalCar的协议和实现了该协议的三个类,接下来我们看CarSelector.swift
CarSelector.swift
class CarSelector {
class func selectCar(passengers:Int) -> String? {
var car:RentalCar?
switch (passengers) {
case 0...1:
car = Sports()
case 2...3:
car = Compact()
case 4...8:
car = SUV()
default:
car = nil
}
return car?.name
}
}
CarSelector类定义了一个类方法selectCar,它根据乘客数量来返回一个车型的名字。下面我们看main.swift:
main.swift
import Foundation
let passengers = [1, 3, 5]
for p in passengers {
print("\(p) passengers: \(CarSelector.selectCar(p)!)")
}
执行程序,得到输出:
1 passengers: Porsche Boxter
3 passengers: VW Golf
5 passengers: Cadillac Escalade
理解工厂方法模式解决的问题
工厂方法模式解决了当有很多类同时实现了同一个协议而你需要选择一个去实例化的问题。就如同例子中的CarSelector。但是示例中有两个问题,第一个问题就是RentalCar协议没有给CarSelector 带来任何益处。如果我们在RentalCar.swift 中增加一个实现,可以看出来。
RentalCar.swift
protocol RentalCar {
var name:String { get }
var passengers:Int { get }
var pricePerDay:Float { get }
}
// ...other implementation classes omitted for brevity...
class Minivan : RentalCar {
var name = "Chevrolet Express"
var passengers = 14
var pricePerDay:Float = 40
}
CarSelector.swift
class CarSelector {
class func selectCar(passengers:Int) -> String? {
var car:RentalCar?
switch (passengers) {
case 0...1:
car = Sports()
case 2...3:
car = Compact()
case 4...8:
car = SUV()
case 9...14:
car = Minivan()
default:
car = nil
}
return car?.name
}
}
从上面可以看出CarSelector类必须清楚的知道RentalCar协议里面的每一个它想使用的实现的类,而且也必须知道每一个实现的类该什么时候被创建。假如我们改变 Sports类让它有4个轮子,那么我们就必须相应的更新 CarSelector类。
第二个问题是当我们增加组件时,选择实现的类会变得很散乱。就像下面:
PriceCalculator.swift
class PriceCalculator {
class func calculatePrice(passengers:Int, days:Int) -> Float? {
var car:RentalCar?
switch (passengers) {
case 0...1:
car = Sports()
case 2...3:
car = Compact()
case 4...8:
car = SUV()
case 9...14:
car = Minivan()
default:
car = nil
}
return car == nil ? nil : car!.pricePerDay * Float(days)
}
}
理解工厂方法模式
工厂方法模式将选择实现类的逻辑封装在一个请求组件可以调用的方法里。只暴露协议或着基类给请求者并且隐藏实现的细节和它们之间的关系。
第一步操作是请求组件调用工厂方法,并且提供用来选择需要实现的类的参数。第二部操作是工厂方法通过传入的参数判断哪一个类被实例化。最后一步创建实例并返回给请求组件。
请求组件不需要知道实现协议的类之间的关系,甚至不需要知道它们是否存在。因为工厂方法返回类型是协议或者基类并不是实现协议的类。
实现工厂方法
工厂方法模式的核心就是-方法。这个方法封装了选择实现类的逻辑决定,通过传入的参数来决定返回的实现对象。
-
定义一个全局的工厂方法
实现这个模式最简单的方法就是定义一个全局的方法。全局方法对于整个应用程序来说都是可用的。
RentalCar.swift
func createRentalCar(passengers:Int) -> RentalCar? {
var car:RentalCar?
switch (passengers) {
case 0...1:
car = Sports()
case 2...3:
car = Compact()
case 4...8:
car = SUV()
case 9...14:
car = Minivan()
default:
car = nil
}
return car
}
protocol RentalCar {
var name:String { get }
var passengers:Int { get }
var pricePerDay:Float { get }
}
class Compact : RentalCar {
var name = "VW Golf"
var passengers = 3
var pricePerDay:Float = 20
}
class Sports : RentalCar {
var name = "Porsche Boxter"
var passengers = 1
var pricePerDay:Float = 100
}
class SUV : RentalCar {
var name = "Cadillac Escalade"
var passengers = 8
var pricePerDay:Float = 75
}
class Minivan : RentalCar {
var name = "Chevrolet Express"
var passengers = 14
var pricePerDay:Float = 40
}
这看起来也只是一个小的改变,全局方法createRentalCar的内容和上面CarSelector类和PriceCalculator 类几乎一样。即使这样,这种改变也是意义重大的。接着我们修改CarSelector类:
CarSelector.swift
class CarSelector {
class func selectCar(passengers:Int) -> String? {
return createRentalCar(passengers)?.name
}
}
CarSelector类不仅代码少了很多,而且只和全局方法createRentalCar和RentalCar协议有依赖关系了。再也不用知道Compact等实现类的具体内容了,仅仅知道调用了全局方法createRentalCar并且返回了一个实现RentalCar协议的对象。下面可以对PriceCalculator类做相应的修改:
PriceCalculator.swift
class PriceCalculator {
class func calculatePrice(passengers:Int, days:Int) -> Float? {
let car = createRentalCar(passengers)
return car == nil ? nil : car!.pricePerDay * Float(days)
}
}
-
使用基类
使用全局方法虽然有效,但是感觉有点和协议以及实现协议的类分离。另一个办法就是不用协议而用基类。看下面的修改:
RentalCar.swift
class RentalCar {
private var nameBV:String
private var passengersBV:Int
private var priceBV:Float
private init(name:String, passengers:Int, price:Float) {
self.nameBV = name
self.passengersBV = passengers
self.priceBV = price
}
final var name:String {
return nameBV
}
final var passengers:Int {
return passengersBV
}
final var pricePerDay:Float {
return priceBV
}
class func createRentalCar(passengers:Int) -> RentalCar? {
var car:RentalCar?
switch (passengers) {
case 0...3:
car = Compact()
case 4...8:
car = SUV()
default:
car = nil
}
return car
}
}
class Compact : RentalCar {
private init() {
super.init(name: "VW Golf", passengers: 3, price: 20)
}
// functionality specific to compact cars goes here
}
class SUV : RentalCar {
private init() {
super.init(name: "Cadillac Escalade", passengers: 8, price: 75)
}
// functionality specific to SUVs cars goes here
}
我们把 RentalCar协议换成了 RentalCar类,定义了私有的构造方法,并且定义了一个类方法用来封装选择Compact等子类的实现对象。同时我们修改 CarSelector类和PriceCalculator类:
CarSelector.swift
class CarSelector {
class func selectCar(passengers:Int) -> String? {
return RentalCar.createRentalCar(passengers)?.name
}
}
PriceCalculator.swift
class PriceCalculator {
class func calculatePrice(passengers:Int, days:Int) -> Float? {
let car = RentalCar.createRentalCar(passengers)
return car == nil ? nil : car!.pricePerDay * Float(days)
}
}
-
委托决定权给更深一层的类
如果面对多层继承关系的类,那么将一部分逻辑委托给它们自己会很有用。请看下面:
RentalCar.swift
class RentalCar {
private var nameBV:String
private var passengersBV:Int
private var priceBV:Float
private init(name:String, passengers:Int, price:Float) {
self.nameBV = name
self.passengersBV = passengers
self.priceBV = price
}
final var name:String {
return nameBV
}
final var passengers:Int {
return passengersBV
}
final var pricePerDay:Float {
return priceBV
}
class func createRentalCar(passengers:Int) -> RentalCar? {
var carImpl:RentalCar.Type?
switch (passengers) {
case 0...3:
carImpl = Compact.self
case 4...8:
carImpl = SUV.self
default:
carImpl = nil
}
return carImpl?.createRentalCar(passengers)
}
}
class Compact : RentalCar {
private convenience init() {
self.init(name: "VW Golf", passengers: 3, price: 20);
}
private override init(name: String, passengers: Int, price: Float) {
super.init(name: name, passengers: passengers, price: price)
}
override class func createRentalCar(passengers:Int) -> RentalCar? {
if (passengers < 2) {
return Compact()
} else {
return SmallCompact()
}
}
}
class SmallCompact : Compact {
private init() {
super.init(name: "Ford Fiesta", passengers: 3, price: 15)
}
}
class SUV : RentalCar {
private init() {
super.init(name: "Cadillac Escalade", passengers: 8, price: 75)
}
override class func createRentalCar(passengers:Int) -> RentalCar? {
return SUV()
}
}
这里我们又创建了一个Compact类的子类SmallCompact类。原本应该在 RentalCar类里面增加SmallCompact类的逻辑,但是这会变得很奇怪。
另一个可以选择的方法就是将选择权交给子类。Compact 类才是最了解什么时候SmallCompact类才是最合适的。
工厂方法模式的变形
工厂方法模式可以和其他模式组合在一起实现更复杂的的应用结构。最常用的组合是工厂方法模式和单例模式。用工厂方法模式来选择一个单例对象。
RentalCar.swift
class RentalCar {
private var nameBV:String
private var passengersBV:Int
private var priceBV:Float
private init(name:String, passengers:Int, price:Float) {
self.nameBV = name
self.passengersBV = passengers
self.priceBV = price
}
final var name:String {
return nameBV
}
final var passengers:Int {
return passengersBV
}
final var pricePerDay:Float {
return priceBV
}
class func createRentalCar(passengers:Int) -> RentalCar? {
var carImpl:RentalCar.Type?
switch (passengers) {
case 0...3:
carImpl = Compact.self
case 4...8:
carImpl = SUV.self
default:
carImpl = nil
}
return carImpl?.createRentalCar(passengers)
}
}
class Compact : RentalCar {
static let compactSharedInstance = Compact()
private convenience init() {
self.init(name: "VW Golf", passengers: 3, price: 20);
}
private override init(name: String, passengers: Int, price: Float) {
super.init(name: name, passengers: passengers, price: price)
}
override class func createRentalCar(passengers:Int) -> RentalCar? {
if (passengers < 2) {
return compactSharedInstance
} else {
return SmallCompact.smallCompactSharedInstance
}
}
}
class SmallCompact : Compact {
static let smallCompactSharedInstance = SmallCompact()
private init() {
super.init(name: "Ford Fiesta", passengers: 3, price: 15)
}
}
class SUV : RentalCar {
private init() {
super.init(name: "Cadillac Escalade", passengers: 8, price: 75)
}
override class func createRentalCar(passengers:Int) -> RentalCar? {
return SUV()
}
}
Tip:你可以利用工厂方法模式去管理一组相关的对象池。如果你这么做,记住要为每一个类的实现创建对象池。如果用所有对象用一个对象池,调用者会得到错误的实现对象。