在Swift中定制类集群
这是一个比较常见的devise模式:
https://stackoverflow.com/a/17015041/743957
它允许你从init
调用中返回一个子类。
我试图找出使用Swift实现相同的事情的最佳方法。
我知道很可能有一个更好的方法来实现Swift同样的事情。 但是,我的课程将由我无法控制的现有Obj-C库进行初始化。 所以它需要以这种方式工作,并可以从Obj-C调用。
任何指针将非常感激。
我不相信这个模式可以在Swift中直接支持,因为初始化程序不像在Objective C中那样返回一个值 – 所以你没有机会返回一个替代的对象实例。
你可以使用types方法作为对象工厂 – 一个相当人为的例子是 –
class Vehicle { var wheels: Int? { get { return nil } } class func vehicleFactory(wheels:Int) -> Vehicle { var retVal:Vehicle if (wheels == 4) { retVal=Car() } else if (wheels == 18) { retVal=Truck() } else { retVal=Vehicle() } return retVal } } class Car:Vehicle { override var wheels: Int { get { return 4 } } } class Truck:Vehicle { override var wheels: Int { get { return 18 } } }
main.swift
let c=Vehicle.vehicleFactory(4) // c is a Car println(c.wheels) // outputs 4 let t=Vehicle.vehicleFactory(18) // t is a truck println(t.wheels) // outputs 18
创build类簇的“swifty”方法实际上是暴露协议而不是基类。
编译器显然禁止在协议或协议扩展上使用静态函数。
直到例如https://github.com/apple/swift-evolution/pull/247 (工厂初始值设定项)被接受和执行,我唯一能find的方法如下:
import Foundation protocol Building { func numberOfFloors() -> Int } func createBuilding(numberOfFloors numFloors: Int) -> Building? { switch numFloors { case 1...4: return SmallBuilding(numberOfFloors: numFloors) case 5...20: return BigBuilding(numberOfFloors: numFloors) case 21...200: return SkyScraper(numberOfFloors: numFloors) default: return nil } } private class BaseBuilding: Building { let numFloors: Int init(numberOfFloors:Int) { self.numFloors = numberOfFloors } func numberOfFloors() -> Int { return self.numFloors } } private class SmallBuilding: BaseBuilding { } private class BigBuilding: BaseBuilding { } private class SkyScraper: BaseBuilding { }
。
// this sadly does not work as static functions are not allowed on protocols. //let skyscraper = Building.create(numberOfFloors: 200) //let bigBuilding = Building.create(numberOfFloors: 15) //let smallBuilding = Building.create(numberOfFloors: 2) // Workaround: let skyscraper = createBuilding(numberOfFloors: 200) let bigBuilding = createBuilding(numberOfFloors: 15) let smallBuilding = createBuilding(numberOfFloors: 2)
由于init()
不像Objective-C中的-init
那样返回值,因此使用工厂方法似乎是最简单的select。
一个诀窍是把你的初始化器标记为private
,就像这样:
class Person : CustomStringConvertible { static func person(age: UInt) -> Person { if age < 18 { return ChildPerson(age) } else { return AdultPerson(age) } } let age: UInt var description: String { return "" } private init(_ age: UInt) { self.age = age } } extension Person { class ChildPerson : Person { let toyCount: UInt private override init(_ age: UInt) { self.toyCount = 5 super.init(age) } override var description: String { return "\(self.dynamicType): I'm \(age). I have \(toyCount) toys!" } } class AdultPerson : Person { let beerCount: UInt private override init(_ age: UInt) { self.beerCount = 99 super.init(age) } override var description: String { return "\(self.dynamicType): I'm \(age). I have \(beerCount) beers!" } } }
这会导致以下行为:
Person.person(10) // "ChildPerson: I'm 10. I have 5 toys!" Person.person(35) // "AdultPerson: I'm 35. I have 99 beers!" Person(35) // 'Person' cannot be constructed because it has no accessible initializers Person.ChildPerson(35) // 'Person.ChildPerson' cannot be constructed because it has no accessible initializers
它不像Objective C那么好,因为private
意味着所有的子类都需要在同一个源文件中实现,而且Person.person(x)
(或者Person.create(x)
或者其他)只是Person(x)
,但实际上,它的工作原理是一样的。
为了能够将字面实例化为Person(x)
,可以将Person
转换为包含实际基类的私有实例的代理类,并将所有内容转发给它。 没有消息转发,这适用于具有很less属性/方法的简单接口,但是对于更复杂的任何东西来说,这些接口是不便的
我认为实际上可以使用运行时函数在Swift中实现Cluster模式。 重点是在初始化时用新的子类replace你的新对象的类。 下面的代码工作正常,但我认为应该更多地关注子类的初始化。
class MyClass { var name: String? convenience init(type: Int) { self.init() var subclass: AnyClass? if type == 1 { subclass = MySubclass1.self } else if type == 2 { subclass = MySubclass2.self } object_setClass(self, subclass) self.customInit() } func customInit() { // to be overridden } } class MySubclass1 : MyClass { override func customInit() { self.name = "instance of MySubclass1" } } class MySubclass2 : MyClass { override func customInit() { self.name = "instance of MySubclass2" } } let myObject1 = MyClass(type: 1) let myObject2 = MyClass(type: 2) println(myObject1.name) println(myObject2.name)