问题 Swift中的自定义类集群


这是一种相对常见的设计模式:

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

它允许您从您的子类返回一个子类 init 调用。

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

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

任何指针都将非常感激。


4532
2018-06-03 11:33


起源



答案:


我不相信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

7
2018-06-03 12:27



啊好吧。不幸的是,我正在使用的库直接调用 initWithDictionary:所以我不认为我能做任何事情。我会尝试至少用它来做子类。感谢您的帮助 :) - James Billingham
在这种情况下,我唯一能想到的是ObjectiveC适配器类,它实现了initWithDictionary并调用了Swift工厂方法。 - Paulw11
啊,是的,是的。我不是特别需要使用Swift - 只是方便的话。 - James Billingham
男人,采取在Obj-C中工作得很漂亮并在Swift中完全手铐的方法。更简单和可重用的概念总是比一大堆专业功能更好。 Obj-C中的初始化器只是返回对象的方法的想法很棒,现在我们又回到了其他语言中相同的有限废话。我开始想知道Swift现在的意义是什么:它比Objective-C更复杂,更不直观,功能更差,但它在某种程度上应该是未来? - devios1


答案:


我不相信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

7
2018-06-03 12:27



啊好吧。不幸的是,我正在使用的库直接调用 initWithDictionary:所以我不认为我能做任何事情。我会尝试至少用它来做子类。感谢您的帮助 :) - James Billingham
在这种情况下,我唯一能想到的是ObjectiveC适配器类,它实现了initWithDictionary并调用了Swift工厂方法。 - Paulw11
啊,是的,是的。我不是特别需要使用Swift - 只是方便的话。 - James Billingham
男人,采取在Obj-C中工作得很漂亮并在Swift中完全手铐的方法。更简单和可重用的概念总是比一大堆专业功能更好。 Obj-C中的初始化器只是返回对象的方法的想法很棒,现在我们又回到了其他语言中相同的有限废话。我开始想知道Swift现在的意义是什么:它比Objective-C更复杂,更不直观,功能更差,但它在某种程度上应该是未来? - devios1


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

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

直到例如 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)

4
2018-06-20 15:18





以来 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) 或者其他)而不是简单的 Person(x)但实际上,它的工作原理是一样的。

能够按字面意思实例化 Person(x)你可以转 Person 到代理类,它包含实际基类的私有实例,并将所有内容转发给它。如果没有消息转发,这适用于具有少量属性/方法的简单接口,但对于任何更复杂的东西都会变得难以处理:P


1
2017-09-13 14:05





我认为实际上Cluster模式可以使用运行时函数在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)

0
2018-06-07 10:36



我并不十分相信这会起作用。原因是 - 如果有其他字段怎么办? MySubclass1?用于初始化的内存分配 MyClass 不正确。只是在运行时重写类不会处理内存会吗? - James Billingham
@JamesBillingham同意。但是有两个正确的解决方案:将数据存储在关联对象中(也可以通过运行时获得)或向基类添加更多字段(甚至是子类可以存储其数据的字典)。这当然不会为您的实现细节增添美感,但会使类接口保持良好(主观)并且不受工厂方法的影响。 Swift中集群实现的另一个缺点是它不会让你返回nil。因此,如果您可以使用类级创建方法,那么绝对对象工厂是最安全的解决方案。 - onemorething
是的,这绝对不是我们的可能性。我们需要使用我们依赖的库的实际属性。此外,我认为由于Swift进行了优化,即使您非常小心,仍然存在分配问题的可能性。 - James Billingham