15

这是一种比较常见的设计模式:

https://stackoverflow.com/a/17015041/743957

它允许您从init调用中返回子类。

我试图找出使用 Swift 实现相同目标的最佳方法。

我知道很可能有更好的方法可以用 Swift 实现同样的目标。但是,我的课程将由我无法控制的现有 Obj-C 库初始化。所以它确实需要以这种方式工作并且可以从 Obj-C 调用。

任何指针将不胜感激。

4

7 回答 7

9

我不相信 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
于 2014-06-03T12:27:14.277 回答
4

创建类集群的“快速”方式实际上是公开协议而不是基类。

显然,编译器禁止协议或协议扩展上的静态函数。

直到例如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)
于 2015-06-20T15:18:36.597 回答
1

由于init()不像-initObjective 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

于 2015-09-13T14:05:00.320 回答
0

我认为实际上集群模式可以使用运行时函数在 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)
于 2014-06-07T10:36:24.180 回答
0
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,您可以根据更有效的方法来决定实例化或切换到哪一个。

于 2020-08-07T15:34:35.273 回答
0

有一种方法可以实现这一点。这是好是坏的做法是另一个讨论。

我个人使用它来允许扩展插件中的组件,而无需将其余代码暴露给扩展知识。这遵循了 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 并且不能在协议元类型上调用静态成员函数。这太糟糕了,因为该解决方案看起来更干净。

于 2020-08-29T01:42:18.320 回答
0

我们可以利用编译器的怪癖——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 中的工作方式相同,其中一些类(例如NSStringNSArrayNSDictionary)根据初始化时给出的值返回不同的子类。

于 2020-11-03T15:55:48.653 回答