Creational Pattern: Abstract Factory
Here is an article that details a creational design pattern that allows us to create a family of products in a flexible and extensible manner.
What is it?
In the last article, we learned about the factory method pattern. Then, we saw how we could model the variants of the F-16 using the factory method. But this article will cover how to represent numerous airplanes other than the F-16. For example, say a client buys a Boeing 747 for his CEO to travel in and wants your software to support this new aircraft type.
The abstract factory pattern solves the problem of creating families of related products. For instance, F-16 needs an engine, a cockpit, and wings. The Boeing-747 would require the same parts, but they would be specific to Boeing. Any airplane would need these three related parts; however, the parts will be plane-specific. Can you see a pattern emerge here? First, we need a framework for creating the associated parts for each airplane.
The abstract factory pattern is a design pattern that allows you to create families of related or dependent objects without specifying their concrete classes.
Class Diagram
The class diagram includes the following entities:
- Abstract Factory
- Concrete Factory
- Abstract Product
- Concrete Product
- Client
Example
The abstract factory pattern allows you to create a family of related objects without specifying their concrete classes. Let’s consider an example. Say you are creating simulation software for the aviation industry and need to represent different aircraft as objects. But before representing an aircraft, you also need to define the other pieces of an aircraft as objects. Let’s pick three: wings, cockpit, and engine. Now say the first aircraft you want to represent is the mighty F-16. You’ll probably write three classes, one for each piece specific to the F-16. In your code, you’ll likely consume these just created three classes like this:
This code snippet appears harmless but can cause severe headaches if your simulation software takes off and you need to expand it to other aircraft. These are some of the problems with the above code:
- The concrete classes for the three parts have been exposed to the consumer directly.
- The F-16 has several variants with different engines. If you want to return an engine object matching the variant, you’ll need to subclass the F-16Engine class, which would also necessitate changing the consumer snippet.
- The List in the code snippet is declared parametrized with a concrete class; if you add another aircraft engine to your program, it won’t be recognized by the List, even though engines for all aircraft are somewhat similar.
We will fix these issues individually and see how the abstract factory pattern emerges.
Treat an interface as an abstract concept rather than an implementation
Good object-oriented design means hiding the concrete classes used in your application and exposing interfaces to clients. An object responds to a set of requests that can be captured by an interface implemented by the object’s class. Clients should know what demands an object responds to rather than the implementation.
In our example, we’ll create an interface that exposes a method called start(). The F16Engine class will then change like so:
See how the corresponding consumer code changes with the above change.
The consumer code is now free of the implementation details of how the F-16 engine works and what class implements it. However, we still don’t want to reveal the F16Engine() part of the code. We want to keep our consumers guessing about what class we’re instantiating. This is discussed next.
Creating a factory
Instead of creating objects in client code, we’ll have a factory class that creates the requested objects and returns them to the client. We’ll call this class F16Factory because it can produce various parts of the F16 military aircraft and deliver them to the requesting client. The class would take the following shape.
Suppose now that we pass in the F16Factory object to the client code as a constructor argument, and it can then create objects like so:
Note how this setup allows us to change the concrete class representing the F16Engine as long as it commits to the IEngine interface. We can rename, enhance or modify our class without causing a breaking change in the client. Also note that by changing only the factory class passed into the client constructor, we can provide the client with different parts for a completely new aircraft.
Factory of Factories: Large factory that makes other factories
Wouldn’t it be great to use the same client snippet for different kinds of aircraft, such as a Boeing 747 or a Russian MiG-29? If all the factories implementing createEngine() agree on a standard interface, our client code will keep working for all aircraft factories. But all the factories would have to commit to this common interface whose methods they will implement, and this familiar interface is an abstract factory.
Implementation
Let’s start with an interface defining the methods that different aircraft factories need to implement. The client code is written against this interface but will be composed at runtime with a concrete factory.
When we refer to “interface,” we mean a Java interface or an abstract class. In this case, we could have used an abstract class if there were default implementations for any of the products. The create methods don’t return concrete products — they return interfaces to decouple the factory consumers from the concrete implementation of parts.
The formal definition of the abstract factory pattern says that it’s a design pattern that defines an interface for creating families of related products without specifying the concrete classes. In this case, the IAircraftFactory is that interface, and its create methods aren’t returning substantial parts but interfaces implemented by the concrete parts’ classes.
Next, let’s set up the factories for our two aircraft
A concrete factory will produce an F-16 or Boeing-specific engine, cockpit, and wings. Each part has a corresponding product interface that we don’t list here for brevity’s sake. The product interfaces representing the parts would be:
IEngine
ICockpit
IWings
All the create methods are factory methods that have been overridden. The factory method pattern is used when implementing an abstract factory. We have omitted to list the concrete classes for the engine, wings, and cockpit.
In the previous article, we created a class for F-16 that included a fly() method. This method invoked makeF16(), which constructed an instance of F-16, then invoked taxi(). Once it was on the runway, it then flew away. We can apply this same approach to our Boeing-747 example. All aircraft follow the same pattern: get manufactured, taxi on the runway, and then fly away. We can thus create a class for an aircraft that does these three tasks. Note how we aren’t creating separate classes to represent the two aircraft (i.e., F-16 and Boeing-747) but rather a single Aircraft class that can represent both.
For the time being, we will keep the makeAircraft method empty. So let’s first see how a client will request F-16 and Boeing-747 objects.
To add a constructor to our Aircraft class, we’ll need to create a new instance variable called factory. The factory stores the object that will be responsible for creating aircraft parts. We can get a different aircraft by composing the aircraft object with another factory. The complete version of the Aircraft class is below:
The client must instantiate the right factory, which it passes to the Aircraft class. The Aircraft class represents the client. We could have created an IAircraft interface to represent all the aircraft our factory can create, but for our limited example, it’s unnecessary.
The resulting code is easily extended and modified.
To continue our example from the factory method pattern article, we can use either subclass the F16Factory class to create factories for the A and B variants of F-16 or parametrize it with an enum specifying the variant model and return the right part in a switch statement.
Other Examples
- The abstract factory pattern helps build families of related products. For example, if your library provides fancy UI widgets and supports macOS and Windows, you may need a family of products that work on those platforms. Similarly, themes used in IDEs can be another example. For example, if your IDE supports light and dark themes, then it may use the abstract factory pattern to create widgets that belong to the light or dark theme by varying the concrete factory that makes the widgets.
javax.xml.parsers.DocumentBuilderFactory.newInstance()
will return you a factory.javax.xml.transform.TransformerFactory.newInstance()
will return you a factory.
Caveats
- The factory method and abstract factory pattern are similar in that they help manage object creation. The difference between the two patterns lies in their motivations. The factory method pattern is usually responsible for creating a single product, whereas an abstract factory pattern creates entire families of related products. Furthermore, we use inheritance in the factory method pattern to construct more specialized products. Finally, we practice object composition in an abstract factory pattern by passing in factories consumed to create the desired outcomes.
- We create a concrete factory when we add a new aircraft to our fleet. But suppose a helicopter is added to the fleet and requires a part that an aircraft doesn’t have. In that case, we’ll need to extend the IAircraftFactory interface with another creative method for the part required only by the helicopter. This will cascade the change to existing factories needing to return null since the new component isn’t part of the jets.
- Concrete factories are best implemented as singleton objects.
Other articles in the Creational Pattern series
- Creational Pattern: Singleton
- Creational Pattern: Prototype
- Creational Pattern: Factory Method
- Creational Pattern: Builder
My other publications: https://ercindedeoglu.github.io/