【问题标题】:Swift: Creating a factory function that takes a class type as a parameter, and outputs a constructor of that classSwift:创建一个以类类型为参数的工厂函数,并输出该类的构造函数
【发布时间】:2016-01-12 21:21:11
【问题描述】:

我想创建一个工厂函数,它接受一个类类型并返回一个构造函数,以便以后可以使用该构造函数创建该类的实例。

假设我有两个类,Apple 和 Orange,它们都是 Fruit 的子类。它们需要使用 unknownNumber 进行初始化,我稍后才会知道。

class Apple: Fruit {
    init(unknownNumber: Int) {
        ...
    }
}

class Orange: Fruit {
    init(unknownNumber: Int) {
        ...
    }
}

我想创建一个接受 Class 类型的工厂函数,以便稍后调用此函数并使用 unknownNumber 初始化 Fruit 的特定子类。

//concept:
func makeFruit(typeOfFruit) -> (Int) -> Fruit {
    return { (unknownNumber: Int) -> Fruit in
        return typeOfFruit(unknownNumber)
    }
}

要创建一个 orangeFactory,我可以这样做:

let orangeFactory = makeFruit(Orange)    

// then at a later time when I have the unknown number
let orangeInstance = orangeFactory(unknownNumber)

我知道简单地将 unknownNumber 设为惰性变量的选项,但在我的具体情况下,unknownNumber 不仅仅是一个数字,它还涉及其他进程,所以我只想创建对象当我有一切可用时,保持结构简单。

这样的事情在 Swift 中是否可行?我在网上研究了一段时间,似乎找不到任何直接的答案。任何帮助将不胜感激!

【问题讨论】:

    标签: swift inheritance constructor swift2


    【解决方案1】:

    让我们向后工作。在makeFruit 函数中,您需要将typeOfFruit 参数声明为Fruit 超类的元类型并显式引用初始化程序:

    func makeFruit(typeOfFruit: Fruit.Type) -> (Int) -> Fruit {
        return { (unknownNumber: Int) -> Fruit in
            return typeOfFruit.init(unknownNumber: unknownNumber)
        }
    }
    

    您只能访问元类型上的 required 初始化程序,因此需要将 init 标记为:

    class Fruit {
        required init(unknownNumber: Int) {
            // ...
        }
    }
    

    其余的应该可以工作:

    let orangeMaker = makeFruit(Orange.self)
    let tenOranges = orangeMaker(10)
    

    【讨论】:

    • 这是一个很好的答案。这个方法看起来很简单,但也需要了解 Swift 对象创建模型的许多小特性。我现在正在尝试这个,如果它有效,我会尽快通知你!
    【解决方案2】:

    如果您打算使用类本身作为工厂条目的标识符,那么您实际上并不需要工厂。工厂模式在任意标识符和对应的对象类之间创建了间接关系。

    在 Swift 中执行此操作的一个简单方法是使用字典:

    var fruitFactory:[String:Fruit.Type] = [:]
    
    fruitFactory["Apple"]   = Apple.self
    fruitFactory["Orange"]  = Orange.self
    fruitFactory["Banana"]  = Fruit.self
    fruitFactory["Grape"]   = Fruit.self
    
    let tenOranges = fruitFactory["Orange"]!.init(unknownNumber:10)
    

    请注意,您需要将 Fruit 类中的初始化程序标记为所需的才能工作。

    【讨论】:

    • 非常感谢您的回答。一旦我了解了在 Class.self 上调用 .init() 的可能性,它打开了很多门!我同意你的方法是最简单的,但我觉得有必要选择 Nate 的答案作为接受的答案,因为他准确地回答了我的要求,而且它也完美无缺!
    【解决方案3】:

    您可以将Fruit 声明为公开所需init 方法的协议,并利用Switf 中的泛型支持:

    protocol Fruit {
        init(unknownNumber: Int)
    }
    
    class Apple: Fruit {
        required init(unknownNumber: Int) {
    
        }
    }
    
    class Orange: Fruit {
        required init(unknownNumber: Int) {
    
        }
    }
    
    func makeFruit<T: Fruit>(cls: T.Type) -> Int -> T {
        return { T(unknownNumber: $0) }
    }
    
    makeFruit(Apple.self)(10)  // returns an Apple
    makeFruit(Orange.self)(15) // returns an Orange
    

    这也使您的类型安全,因为makeFruit 函数的结果与cls 参数指定的类型相同。

    请注意,这不是工厂函数,而只是转发函数。但是你可以更进一步,为一些水果自定义makeFruit,这就是使它成为工厂函数的原因:

    class Apple: Fruit {
        required init(unknownNumber: Int) {
    
        }
    
        init(color: String, unknownNumber: Int) {
    
        }
    }
    
    func makeFruit<T: Fruit>(cls: T.Type) -> Int -> T {
        return { T(unknownNumber: $0) }
    }
    
    func makeFruit(cls: Apple.Type) -> Int -> Apple {
        return { Apple(color: "red", unknownNumber: $0) }
    }
    
    makeFruit(Orange.self)(15)  // an Orange
    makeFruit(Apple.self)(10)   // a red Apple
    

    假设Apple 类有一个接受颜色的初始化器,我们可以为这个特定的类覆盖makeFruit 并传递一个默认颜色(或者可能是一个计算出来的颜色,取决于工厂规范)。这不会失去 Swift 的类型安全性。

    【讨论】:

    • 亲爱的反对者!请让我们知道,您在这个答案中看到了什么错误。所有答案(Nate's, Cristik's ad Alain's)都使用基本相同的方法,所有答案仅在小细节上有所不同。为什么 Cristik 的回答被否决???请!如果有人不喜欢某个答案,请告诉我们原因!
    • 嗨 Cristik,非常感谢您的周到回答。我以前没有考虑过使用泛型的方法,你的回答确实教会了我一些东西。我已经接受了 Nate 的回答,因为它以非常直接的方式提供了我所要求的内容,但我也非常感谢您的意见。很抱歉有人对这个答案投了反对票;我已经投票了。
    猜你喜欢
    • 2021-02-07
    • 1970-01-01
    • 2021-02-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-14
    • 2017-09-14
    • 2019-03-23
    相关资源
    最近更新 更多