这是一种比较常见的设计模式:
https://stackoverflow.com/a/17015041/743957
它允许您从init
调用中返回子类。
我试图找出使用 Swift 实现相同目标的最佳方法。
我知道很可能有更好的方法可以用 Swift 实现同样的目标。但是,我的课程将由我无法控制的现有 Obj-C 库初始化。所以它确实需要以这种方式工作并且可以从 Obj-C 调用。
任何指针将不胜感激。
这是一种比较常见的设计模式:
https://stackoverflow.com/a/17015041/743957
它允许您从init
调用中返回子类。
我试图找出使用 Swift 实现相同目标的最佳方法。
我知道很可能有更好的方法可以用 Swift 实现同样的目标。但是,我的课程将由我无法控制的现有 Obj-C 库初始化。所以它确实需要以这种方式工作并且可以从 Obj-C 调用。
任何指针将不胜感激。
我不相信 Swift 可以直接支持这种模式,因为初始化程序不会像在 Objective C 中那样返回一个值——所以你没有机会返回一个备用对象实例。
您可以将类型方法用作对象工厂 - 一个相当人为的示例是 -
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
创建类集群的“快速”方式实际上是公开协议而不是基类。
显然,编译器禁止协议或协议扩展上的静态函数。
直到例如https://github.com/apple/swift-evolution/pull/247(工厂初始化程序)被接受和实施,我能找到的唯一方法是:
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()
不像-init
Objective C 中那样返回值,使用工厂方法似乎是最简单的选择。
一个技巧是将初始化器标记为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)
或其他)而不是 simple Person(x)
,但实际上,它的工作原理相同。
为了能够逐字地实例化为Person(x)
,您可以Person
变成一个代理类,其中包含实际基类的私有实例并将所有内容转发给它。如果没有消息转发,这适用于具有很少属性/方法的简单接口,但对于更复杂的东西它变得笨拙:P
我认为实际上集群模式可以使用运行时函数在 Swift 中实现。要点是在初始化时将新对象的类替换为子类。下面的代码工作正常,但我认为应该更多地关注子类的初始化。
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)
protocol SomeProtocol {
init(someData: Int)
func doSomething()
}
class SomeClass: SomeProtocol {
var instance: SomeProtocol
init(someData: Int) {
if someData == 0 {
instance = SomeOtherClass()
} else {
instance = SomethingElseClass()
}
}
func doSomething() {
instance.doSomething()
}
}
class SomeOtherClass: SomeProtocol {
func doSomething() {
print("something")
}
}
class SomethingElseClass: SomeProtocol {
func doSomething() {
print("something else")
}
}
基本上,您创建一个类集群继承的协议。然后,您环绕相同类型的实例变量并选择要使用的实现。
例如,如果您正在编写一个在 LinkedList 或原始数组之间切换的数组类,那么 SomeOtherClass 和 SomethingElseClass 可能被命名为 LinkedListImplementation 或 PlainArrayImplementation,您可以根据更有效的方法来决定实例化或切换到哪一个。
有一种方法可以实现这一点。这是好是坏的做法是另一个讨论。
我个人使用它来允许扩展插件中的组件,而无需将其余代码暴露给扩展知识。这遵循了 Factory 和 AbstractFactory 模式将代码与实例化细节和具体实现类解耦的目标。
在示例情况下,切换是在您将添加扩展的类型常量上完成的。这在技术上有点与上述目标相矛盾 - 尽管不是在预知方面。但在你的情况下,开关可能是任何东西——例如轮子的数量。
我不记得这种方法是否在 2014 年可用 - 但现在可用。
import Foundation
struct InterfaceType {
let impl: Interface.Type
}
class Interface {
let someAttribute: String
convenience init(_ attribute: String, type: InterfaceType = .concrete) {
self.init(impl: type.impl, attribute: attribute)
}
// need to disambiguate here so you aren't calling the above in a loop
init(attribute: String) {
someAttribute = attribute
}
func someMethod() {}
}
protocol _Factory {}
extension Interface: _Factory {}
fileprivate extension _Factory {
// Protocol extension initializer - has the ability to assign to self, unlike class initializers.
init(impl: Interface.Type, attribute: String) {
self = impl.init(attribute: attribute) as! Self;
}
}
然后在一个具体的实现文件中......
import Foundation
class Concrete: Interface {
override func someMethod() {
// concrete version of some method
}
}
extension InterfaceType {
static let concrete = InterfaceType(impl: Concrete.self)
}
对于这个例子,Concrete 是“工厂”提供的默认实现。
例如,我使用它来抽象模式对话框如何在最初使用 UIAlertController 并迁移到自定义演示文稿的应用程序中呈现的细节。所有呼叫站点都不需要更改。
这是一个简化版本,在运行时不确定实现类。您可以将以下内容粘贴到 Playground 中以验证其操作...
import Foundation
class Interface {
required init() {}
convenience init(_ discriminator: Int) {
let impl: Interface.Type
switch discriminator {
case 3:
impl = Concrete3.self
case 2:
impl = Concrete2.self
default:
impl = Concrete1.self
}
self.init(impl: impl)
}
func someMethod() {
print(NSStringFromClass(Self.self))
}
}
protocol _Factory {}
extension Interface: _Factory {}
fileprivate extension _Factory {
// Protocol extension initializer - has the ability to assign to self, unlike class initializers.
init(impl: Interface.Type) {
self = impl.init() as! Self;
}
}
class Concrete1: Interface {}
class Concrete2: Interface {}
class Concrete3: Interface {
override func someMethod() {
print("I do what I want")
}
}
Interface(2).someMethod()
Interface(1).someMethod()
Interface(3).someMethod()
Interface(0).someMethod()
请注意,它实际上必须是一个类 -即使它不需要成员存储,Interface
您也不能将其折叠为避免抽象类的协议。这是因为您不能在协议元类型上调用 init 并且不能在协议元类型上调用静态成员函数。这太糟糕了,因为该解决方案看起来更干净。
我们可以利用编译器的怪癖——self
允许在协议扩展中分配——https ://forums.swift.org/t/assigning-to-self-in-protocol-extensions/4942 。
因此,我们可以有这样的东西:
/// The sole purpose of this protocol is to allow reassigning `self`
fileprivate protocol ClusterClassProtocol { }
extension ClusterClassProtocol {
init(reassigningSelfTo other: Self) {
self = other
}
}
/// This is the base class, the one that gets circulated in the public space
class ClusterClass: ClusterClassProtocol {
convenience init(_ intVal: Int) {
self.init(reassigningSelfTo: IntChild(intVal))
}
convenience init(_ stringVal: String) {
self.init(reassigningSelfTo: StringChild(stringVal))
}
}
/// Some private subclass part of the same cluster
fileprivate class IntChild: ClusterClass {
init(_ intVal: Int) { }
}
/// Another private subclass, part of the same cluster
fileprivate class StringChild: ClusterClass {
init(_ stringVal: String) { }
}
现在,让我们试一试:
print(ClusterClass(10)) // IntChild
print(ClusterClass("abc")) // StringChild
这与在 Objective-C 中的工作方式相同,其中一些类(例如NSString
、NSArray
、NSDictionary
)根据初始化时给出的值返回不同的子类。