Provide an interface for creating families of related or dependent objects without
Abstract Factory – GoF
specifying their concrete classes.
Abstract Factory Example
In order to better understand this design pattern, we directly jump in a small example. Assume we are having a game which has a map / landscape (for example Age of Empires or similar strategy games). The game must support different regions such as Forrest, Desert etc. Depending on region it may need different objects. While in the forrest there are trees, in the desert there will be a cactus. In turns out that this way of thinking can be applied to many different objects (e.g. Terrain, Food, etc). It is possible decouple the game from the region entirely if we say there is “vegetation” or there is “terrain”. In this way the high – level game knows only something about the other high – level classes.
But of course the Game class has to use concrete objects. This is where the factory comes in. It provides the central access point to create the concrete objects. Depending of the region it will create different objects. The following UML diagram illustrates that. All classes which are blue are high – level classes. All classes which are green are forrest related classes. All classes which are yellow are desert related classes.
SOLID Principles
Before going into the code we want to add a word about the SOLID principles of OO design. This design pattern is major contributor to these principles. It decouples high – level classes from low – level classes by using interfaces. This leads to a reusable code. Adding new regions does not influence high – level classes (it is Open – Closed). All classes are very cohesive (it follows Single Responsibility). High level classes do no depend on implementation detail (it follows Dependency Inversion).
As a fact the abstract factory pattern can found often because it is easy to understand but still leads to very well separated code.
Code
open class Vegetation class Cactus : Vegetation() class Tree : Vegetation() open class Terrain class Sand : Terrain() class Grass : Terrain()
interface Factory { fun createTerrain() : Terrain fun createVegetation() : Vegetation }
class DesertFactory : Factory { override fun createTerrain() : Terrain { return Sand() } override fun createVegetation() : Vegetation { return Cactus() } }
class ForrestFactory : Factory { override fun createTerrain(): Terrain { return Grass() } override fun createVegetation(): Vegetation { return Tree() } }
class Game(val factory : Factory) { private lateinit var terrain : Terrain private lateinit var tree : Vegetation init { terrain = factory.createTerrain() tree = factory.createVegetation() } }
As you can see the implementation is quite straight forward. Kotlin (as Java) provides different methods to implement polymorphism. The first is to use base classes. This approach was used for the Terrain and Vegetation classes. The second option is to use an interface as it was used for the factory. It depends on the context which you need to use, but we wanted to show both version in the code.
TDD – Test Driven Development
The abstract factory can often be seen in combination with test driven development. As it is said, TDD will lead to a SOLID code, so it is no suprise to find this pattern when doing TDD.
As an example consider a function which calls a URL. If the call is successfull (e.g. status code 200) it will return a string “OK”. Otherwise it returns “FAILURE”.
One implementation could be the following. The function callUrl is the one, which needs to be tested. Internally it uses class called Network, which does the actual Network access (here not implemented). This could be a Coroutine or something similiar.
class Network(private val url: String = "") { private var errorCode = 200 fun isSuccessful() : Boolean { return errorCode == 200 } fun execute() { } } fun callUrl(url : String) : String { val network = Network(url) network.execute() if(network.isSuccessful()) return "OK" return "FAILURE" }
This code however is very diffuclt, if not impossible to test. The Network access is out of the control in the tests. This could be changed, by using the abstract factory pattern to introduce mock network objects. The following example demonstrates that.
interface Network { fun isSuccessful() : Boolean fun execute() } interface NetworkFactory { fun createNetwork(url : String) : Network }
class NetworkImpl(private val url: String = "") : Network{ private var errorCode = 200 override fun isSuccessful() : Boolean { return errorCode == 200 } override fun execute() { } } class NetworkFactoryImpl : NetworkFactory { override fun createNetwork(url : String) : Network { return NetworkImpl(url) } }
class NetworkMock : Network{ override fun isSuccessful() : Boolean { return true } override fun execute() { } } class NetworkMockFactory : NetworkFactory { override fun createNetwork(url : String) : Network { return NetworkMock() } }
The function under test is only slightly changed. By default it uses the concrete implementation of the network factory. During testing it accepts however the factory which returns Mock objects. Since we are in control of this factory, and therefor the mock objects, we can control manually the network behavior. Furthermore for the client using this function nothing has changed. The signature of the function is the same (thanks to the default parameter). Internally, even so only line changed, it trastically improved the code quality because now, the function depends on an interface, instead of the concrete object.
fun callUrl(url : String, factory : NetworkFactory = NetworkFactoryImpl()) : String { val network = factory.createNetwork(url) network.execute() if(network.isSuccessful()) return "OK" return "FAILURE" }
Alternatives / Related patterns
There are some patterns which are used together with the abstract factory or can be used as a replacement.
Builder pattern
The builder pattern is usually used, when the concrete objects are so different, that they do not share the same interface. The construction however is similar.
Singleton pattern
As the factory are usually single instances, it makes sense to control their creation. The singleton pattern makes sure, that only one factory object can be created, and that it is easy to access. The downside however that it reduces somewhat the flexibility.
Decorator pattern
One advantage of using this pattern is that it gives a perfect access point to add additional behavior to objects, by using the decorator patter. For instance in the previous example about the Network class, we could add a decorator which logs every Network access. This can be done, without touching at all the Network class.
class LogNetworkDecorator (private var network : Network) : Network { override fun isSuccessful(): Boolean { return network.isSuccessful() } override fun execute() { // log return network.execute() } } class NetworkFactoryImpl : NetworkFactory { override fun createNetwork(url : String) : Network { var obj = NetworkImpl(url) return LogNetworkDecorator(obj) } }