What is it
This week we’re going to be taking a look at the abstract factory pattern, which allows us to create families of related products to be used together within their implementation. The pattern automatically enforces this behaviour of using only related products together, which allows multiple families of products to be switched between easily.
When to use it
Using a specific design pattern can help us to optimise, reduce and place constraints within the architecture of our applications. In the case of the abstract factory pattern, there are several situations where it can and should be used:
- To begin with, we may want to create a collection of related products that are designed to be used together. The pattern allows us to do so by enforcing the desired family of products to be at the same time.
- We want to configure a system with one of the multiple families of products, again the pattern ensures that at least one, and only one, of the subset of families of products will be used at any time.
- We want our system to have independence between the creation, composition, and representation of its products. The pattern provides this by decoupling the implementation of each of these operations.
- We want to hide the implementations of our products, only revealing the required interface to provide access to their use.
So we may have some ideas now of where this patterns use may be applicable, let’s take a look at a real-world example to help our understanding a little more.
For example sake, let’s say we’ve built an application which allows our users to change the User Interface from either a dark or a light theme. This kind of implementation means that there’s going to be a lot of component re-use, with the difference to the styling attributes depending on the chosen theme. As we previously noted, the abstract factory pattern allows us to easily re-use components as abstract classes and enforce specific concrete subclasses to be used as part of a component family.
So saying this, when it comes to implementing this feature we know that there are several things that need to be enforced within the system:
- We can have any number of theme families made up of themed components, but our system should only be used with one of these at any given time. This means we want the user to only be able to select either the dark theme or the light theme.
- The themed components inside of each theme are designed to be used together as a collection, the application needs to ensure this is enforced. For example, a Dark themed dialog should be used alongside a Dark toolbar only.
- However, we don’t want to reveal the implementation of these components to the client – we just want to provide the interfaces to instantiate the component, therefore we provide an interface to create the components that we require when the theme is selected.
We know that all of the above points are automatically enforced when we’re using the abstract factory pattern, we just need to create the abstract classes and concrete subclasses in-order to complete the implementation.
- We begin by creating our abstract ThemeFactory class. This class declares an interface that can be used to create an instance of each of the themes that we wish to allow the user to theme our application with.
- The client uses this interface to obtain theme instances, with no awareness of the concrete class that is being used during creation.
- For each theme that we wish to include in our application, we declare concrete subclasses of ThemeFactory, each of these classes will implement the operations that are required to instantiate the individually styled component products related to the selected theme. For example, the DarkTheme instance will return both the DarkToolbar and DarkDialog, whereas the LightTheme will return the LightToolbar and LightDialog.
- We also need to create an abstract class for each of the components that are in the themes we wish to provide. Their concrete subclasses are then used to define the implementation details for the desired themes that we’ve enforced. Our ThemeFactory interface declares the methods that can be used to retrieve instances of these objects for each of our abstract component classes.
- Our ThemeFactory also enforces the dependencies that exist between our concrete component classes. For example, the DarkToolbar must be used with the DarkDialog and the LightToolbar must be used with the LightDialog. These constraints are automatically enforced as a result of using the ThemeFactory we’ve created.
And what does this all look like?
Here, you can see how our ThemeFactory controls the use of components throughout the implementation. We being by selecting a theme from our ThemeFactory (Light, Dark), which then depicts which products are then selected when our product interfaces (Toolbar, Dialog) are interacted with by the client.
Now we’ve taken a look at a real-world example, let’s strip away these features and look at the general structure of the abstract factory pattern without any implementation details:
As you can see, this shows us a general class diagram of the patterns implementation details, which can be used to follow for real-world applications. But what responsibilities do each of these classes hold?
- AbstractFactory – This class declares an interface that is used to create abstract product objects. We interact with this interface to allow the client to access instances of our ConcreteFactory concrete subclasses.
- ConcreteFactory – A subclass of the Abstract factory, these concrete classes implement the operations which are used to create concrete product objects. Each Factory has it’s own individual family of products which it creates.
- AbstractProduct – The AbstractProduct class is used to declare an interface for a specific type of product object. When used, the interface will return the product subclass in relation to the ConcreteFactory which is currently being used to handle product instantiation.
- ConcreteProduct – The concrete product is provided by it’s ConcreteFactory and defines the individual implementation details for the specified product through the implementation of its AbstractProduct interface.
- Client – Only uses and has access to the interfaces that are defined by the AbstractFactory and AbstractProduct classes, making it completely unaware of the implementation details of the system.
So why would we use this pattern? Well, there are some great benefits to using the abstract factory pattern in our system design:
- Because a factory class encapsulates responsibilities and process of product object creation, class implementation is isolated from clients – they only have access to these instances through the use of the defined interfaces, giving us control of the application in regards to the classes of products that it can create.
- The abstract factory pattern helps to boost consistency amongst the products in a collection. These products have been crafted to work together, so it should be enforced that an application can only use product objects from a single-family at any given time. As we’ve previously mentioned, the abstract factory pattern allows us to enforce this.
- Product configurations can be switched extremely easily by changing the ConcreteFactory that is being used. Our ConcreteFactory is only used in a single place within our application, and because the class creates and controls complete families of products, simply switching the ConcreteFactory that our application uses means that we can change the whole set of products that are currently being used in our application.
However, the abstract factory pattern can make it hard for us to provide the support of new families of products. For example, say if we wished to add a new Theme to our example form above, we’d have to extend our ConcreteFactory to create a new theme instance and then implement all of it’s interface methods. If our system currently has a lot of AbstractProduct implementations, then this could be a lengthy task for us to carry out.
There are a few things we can bear in mind when it comes to approaching the implementation of this pattern:
- Our application should only require a single instance of the ConcreteFactory class per product family. Because this is the case, we can implement our factory as a Singleton so it can be persisted and reused throughout the lifecycle of our application, instead of instantiating a new instance every time we need to access its methods.
- The ConcreteProduct subclasses in our design have the responsibility of actually creating our products, which we can do by using the Factory Method design pattern (we’ll look at this soon!) to allow our ConcreteFactory class to override each factory method from our product classes in order to declare them – this approach is suitable if we have a small collection of product families, otherwise the Prototype design pattern may be more applications. This pattern allows us to begin by initializing our ConcreteFactory with prototypical instances of every product from the family, which can then be ‘cloned’ to easily create a new instance of a product – essentially providing us with a template for simply creating a new instance. We’ll look at both of these patterns in more detail soon.
And that’s it!
We’ve learned what the abstract factory pattern is, how we can implement it and looked at a real-world example for its use. Whether you’re learning from scratch or refreshing your design pattern knowledge, I hope this deep-dive has been a good companion in understanding how the pattern works.