Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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!

like image 522
gokeji Avatar asked Jan 12 '16 21:01

gokeji


2 Answers

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)
like image 187
Nate Cook Avatar answered Oct 04 '22 12:10

Nate Cook


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.

like image 20
Alain T. Avatar answered Oct 04 '22 14:10

Alain T.