We all know the value of design experience. How many times have you had design déjàvu – that feeling that you’ve solved a problem before but not knowing exactly where or how? If you could remember the details of the previous problem and how you solved it, then you could reuse the experience instead of rediscovering it. However, we don’t do a good job of recording experience in software design for others to use.
The purpose of this tutorial series to share some of the recorded experience in designing object-oriented software as design patterns. Each design pattern names, explains, and when to use with examples in a form that people can use effectively.
Design patterns make it easier to reuse successful designs and architectures. Design Patterns help you choose design alternatives that make the system reusable and avoid alternatives that compromise reusability.
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 difference to the styling attributes depending on the chosen theme.
- A family of components says Background, Text, and Background sounds.
- Several variants of this family, For example, Background Color, Texts, and Background sound available in this varients Light Theme and Dark Theme.
You need a way to create individual components so that they can match other components of the same family. Users get quite mad when they use non-matching/incompatibility components together. Let say white background color with white color text makes no sense.
Also, you don’t want to change existing code when adding new components or families of components to the program.
Abstract Factory Pattern
Abstract Factory is a creational design pattern that lets you create families of related products to be used together within their implementation. The pattern automatically enforces this behavior of using only related products together, which allows multiple families of products to be switched between easily.
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 its 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.
As we previously noted, the abstract factory pattern allows us to easily re-use components as abstract classes and enforce specific concrete sub classes 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 only to 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, the DarkThemed background screen alongside with the light-colored Text makes UI/UX more elegant.
- 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.
When To Use It
- A system should be configured with one of the multiple families of products.
- A family of related product objects is designed to be used together, and you need to enforce this constraint.
- 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.
How to Implement
- Map out a matrix of distinct product types versus variants of these products.
- Declare abstract product interfaces for all product types. Map out a matrix of distinct product types versus variants of these products.
- Then make all concrete product classes implement these interfaces.
- Declare the abstract factory interface with a set of creation methods for all abstract products.
- Implement a set of concrete factory classes, one for each product variant.
- Create a factory initialization code somewhere in the app. It should instantiate one of the concrete factory classes, depending on the application configuration or the current environment. Pass this factory object to all classes that construct products.
- Scan through the code and find all direct calls to product constructors. Replace them with calls to the appropriate creation method on the factory object.
You can download the examples code from my GitHub Project.