Swift: Creating a factory function that takes a class type as a parameter, and outputs a constructor of that class

僤鯓⒐⒋嵵緔 提交于 2019-12-14 03:41:48

问题


I'd like to create a factory function that takes in an class type and returns a constructor, so that I can use that constructor to create an instance of that class later.

Imagine I have two classes, Apple and Orange, which are both subclasses of Fruit. They need to be initialized with an unknownNumber which I will only know about later.

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

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

I'd like to create a factory function that takes in a Class type, so that I can later call this function and initialize the specific subclass of Fruit, with the unknownNumber.

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

To create an orangeFactory, then, I can do:

let orangeFactory = makeFruit(Orange)    

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

I am aware of the option of simply making the unknownNumber a lazy variable, but in my specific case the unknownNumber is not just a number and it involves other processes, so I'd like to only create the object when I have everything available, to keep the structure simple.

Is something like this possible in Swift? I've been researching for a while online and couldn't seem to find any direct answers. Any help would be greatly appreciated!


回答1:


Let's work backwards. In your makeFruit function, you'll need to declare the typeOfFruit parameter as a metatype of your Fruit superclass and explicitly reference the initializer:

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

You can only access required initializers on a metatype, so that init needs to be marked as such:

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

The rest should just work:

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



回答2:


If you are going to use the class itself as the identifier for a factory entry, you don't actually need a factory. The factory pattern creates an indirection between an arbitrary identifier and a corresponding class of object.

A simple way to do this in Swift is to use a dictionary:

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)

note that your initializer in the Fruit class needs to be marked as required for this to work.




回答3:


You can declare Fruit as a protocol that exposes the required init method, and make use of the generics support in 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

This also gets you type safety as the result of the makeFruit function is the same as the type specified by the cls parameter.

Note that this is not a factory function, but merely a forwarding one. But you can go even further and customize makeFruit for some of the fruits, and this is what is making it a factory function:

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

Assuming that the Apple class has an initializer that accept a color, we can override makeFruit for this specific class and pass a default color (or maybe a computed one, depending of the factory specifications). This without losing the type safety that grows on you with Swift.



来源:https://stackoverflow.com/questions/34753997/swift-creating-a-factory-function-that-takes-a-class-type-as-a-parameter-and-o

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!