Abstract Factory Pattern

The Abstract Factory pattern is one of the creational software patterns, described in the book of the Gang-Of-Four. It deals with the problem of creating objects which are somehow logical grouped. This is done by encapsulating a group of individual factories that have a common theme without specifying their concrete classes. The client doesn’t know (or care) which concrete objects it gets from each of these internal factories, since it uses only the generic interfaces of their products.

Our example scenario

Lets imagine a game where the player is located in a certain area (e.g. rainforrest). In the rainforrest there are animals (monkeys), plants (trees) and grounds (grass). There is already a factory class which can create animals, plants and grounds. Additionally is there is a Level class which uses the factory to instantiate objects.

Monkey, Tree and Grass classes

class Monkey;
class Tree;
class Grass;

The RainForrest Factory class

class RainForrestFactory{
    fun createMonkey() : Monkey{
        return Monkey()
    }

    fun createTree() : Tree{
        return Tree()
    }

    fun createGrass() : Grass{
        return Grass()
    }
}

The Level class and usage

class Level(private var factory : RainForrestFactory){
    fun createWorld(){
        var elefant = factory.createSnake()
        var tree = factory.createTree()
        var grass = factory.createGrass()
    }
}

fun main() {
    var factory = RainForrestFactory()
    var level = Level(factory)
    level.createWorld()
}

Until this point this class setup is ok. But what happens, when new areas will be added? Each area could be defined with different animals, grounds and plants. In case of a polar region, for instance, the classes could be penguins, snow and mosses as animals, ground and plants, respectively. Following the example of the rain forrest, the factory would look like the following code.

Penguin, Mosses and Snow classes

class Penguin;
class Mosses;
class Snow;

PolarFactory class

class PolarFactory{
    fun createPenguin() : Penguin{
        return Penguin()
    }

    fun createMosses() : Mosses{
        return Mosses()
    }

    fun createSnow() : Snow{
        return Snow()
    }
}

If we are going to use the PolarFactory side-by-side with the RainForrestFactory it will become a problem. First this approach is not consistent. The client could mix different world with each other. In this context this does not make sense. This inconsistency will lead to errors. Second the client code does depend on concrete implementations. This means the code is not flexible if the client wants to change the world. The only ways to distinguish would be to use if-else statements. This however is bad coding because it will lead again to errors and is dificult to maintain. Furthermore the code is difficult to expand. If the client needs a new world the client side code will not support it. This breaks the open-closed principle. For these reasons, a factory which gives concrete classes is not usefull.

The solution for these problems is to use abstract interfaces for animals, plants and grounds and aswell for the factories. The client code will be changed to be dependend of the abstractions but not on the concretions. First we will group the different animals, plants and ground with specific abstract interfaces.

UML Diagramm Abstract Factory Pattern

UML DIagramm Abstract Factory Pattern

Animals

interface IAnimal

class Camel : IAnimal
class Monkey : IAnimal
class Penguin : IAnimal

Plants

interface IPlant

class Cactus : IPlant
class Mosses : IPlant
class Tree : IPlant

Grounds

interface IGround

class Grass : IGround
class Sand : IGround
class Snow : IGround

Now we have to adapt the factories and to group them with an interface. Note that we changed the names of the functions to be more precise.

Terrain Factories

interface ITerrainFactory{
    fun createAnimal() : IAnimal
    fun createGround() : IGround
    fun createPlant() : IPlant
}

class DessertFactory : ITerrainFactory {
    override fun createAnimal(): IAnimal {
        return Camel()
    }

    override fun createGround(): IGround {
        return Sand()
    }

    override fun createPlant(): IPlant {
        return Cactus()
    }
}

class PolarFactory : ITerrainFactory {
    override fun createAnimal(): IAnimal {
        return Penguin()
    }

    override fun createGround(): IGround {
        return Snow()
    }

    override fun createPlant(): IPlant {
        return Mosses()
    }
}

class RainForrestFactory : ITerrainFactory {
    override fun createAnimal(): IAnimal {
        return Monkey()
    }

    override fun createGround(): IGround {
        return Grass()
    }

    override fun createPlant(): IPlant {
        return Tree()
    }
}

Main function and arbitrary level class

fun main(){
    var factory : ITerrainFactory = DessertFactory()
    var level = Level(factory)

    factory = PolarFactory()
    level = Level(factory)

    factory = RainForrestFactory()
    level = Level(factory)
}

class Level(terrainFactory: ITerrainFactory){
    private var animal = terrainFactory.createAnimal()
    private var plant = terrainFactory.createPlant()
    private var ground = terrainFactory.createGround()
}

Conclusion