Design Patterns VS Design Principles: Abstract Factory
In the “Design Pattens VS Design Principles” series, we look at design patterns and relate them to design principles. In this episode, we examine the Abstract Factory pattern.
Let’s see how Abstract Factory works and what it is useful for, then relate it to a design principle. We will also see a C++ technique to implement Abstract Factory with classes nested in a function.
Design Patterns and Design Principles
What is the difference between design patterns and design principles?
The design patterns we talk about are the collection of patterns described in the popular GoF book:
Design patterns are typical ways to organise the components of a program in typical situations.
Design principles, on the other hand, are general guidelines te help create robust designs. The 9 GRASP principles are described in Craig Larman’s Applying UML and Patterns book:
The 9 GRASP design principles are:
- Low Coupling
- High cohesion
- Creator
- Information expert
- Polymorphism
- Indirection
- Protected Variations
- Pure Fabrication
- Controller
Let’s analyse the GoF design pattern Abstract Factory, and decide to which GRASP principle it relates the most.
What Abstract Factory is useful for
The goal of the Abstract Factory pattern is to make sure that objects are created consistently with each other.
This need arises when several objects can be created in a context, and there are several ways to create those objects. The GoF books uses the term of “family”: there are several families of objects. You can also see it as if there were several brands of objects, and in a given context you want all objects to be created of the same brand.
To do that, the Abstract Factory pattern consists in assigning to one class the responsibility of creating all the objects of a given family or brand.
Concrete factories
Let’s illustrate with a simple example, with an Abstract Factory that builds the parts of a house. In practice, the objects we create in code are more abstract (I’ve used Abstract Factory to create objects related to transactions for example), but let’s use this simple example to illustrate the structure of the pattern.
The objects we want to create are a door, a roof and a wall. There are several types of houses: blue houses, red houses, and yellow houses. And there may be more of them.
When we build a blue house, we want to make sure that all its components are blue. If we were to instantiate each object separately, each time there would be a risk of not instantiating the right type.
To mitigate that risk, we instantiate only one object: the blue house factory. Then we only use this object to create the others. This means that if we get the right factory, we’re guaranteed to get all the components of the right color too.
BlueHouseFactory { Door createDoor() const; // this returns a blue door Roof createRoof() const; // this returns a blue roof Wall createWall() const; // this returns a blue wall };
Note that this is a simple factory that doesn’t use polymorphism on the objects it creates (Door
, etc.). Indeed, there could also be polymorphism in the objects that the factory creates:
class BlueHouseFactory { public: std::unique_ptr<Door> createDoor() const; // this returns a blue door std::unique_ptr<Roof> createRoof() const; // this returns a blue roof std::unique_ptr<Wall> createWall() const; // this returns a blue wall };
With subclasses for each object:
class BlueDoor : public Door { // ... };
And the code for createDoor
would then look like this:
std::unique_ptr<Door> BlueHouseFactory::createDoor() { return std::make_unique<BlueDoor>(); }
Abstract Factory
Now we have one factory, that creates blue components. We go on and introduce other factories, that create other types of components: the RedHouseFactory
and the YellowHouseFactory
.
The goal is to use only one factory in a given context. To do that, we introduce an abstract factory, that can be either one of the above factories:
class HouseFactory { public: virtual std::unique_ptr<Door> createDoor() const = 0; virtual std::unique_ptr<Roof> createRoof() const = 0; virtual std::unique_ptr<Wall> createWall() const = 0; virtual ~HouseFactory() = 0; };
The concrete factories then implement this interface:
class BlueHouseFactory : public HouseFactory { public: std::unique_ptr<Door> createDoor() const override; // this returns a blue door std::unique_ptr<Roof> createRoof() const override; // this returns a blue roof std::unique_ptr<Wall> createWall() const override; // this returns a blue wall }; class RedHouseFactory : public HouseFactory { public: std::unique_ptr<Door> createDoor() const override; // this returns a red door std::unique_ptr<Roof> createRoof() const override; // this returns a red roof std::unique_ptr<Wall> createWall() const override; // this returns a red wall }; class YellowHouseFactory : public HouseFactory { public: std::unique_ptr<Door> createDoor() const override; // this returns a yellow door std::unique_ptr<Roof> createRoof() const override; // this returns a yellow roof std::unique_ptr<Wall> createWall() const override; // this returns a yellow wall };
Creating the factory
The factory is made to create objects, but who creates the factory?
There are various ways to do that. One of them is to instantiate various concrete factories in various places of the code, depending on the context. Given a function that uses a factory:
House buildAHouse(HouseFactory const& houseFactory);
We could call this fonction with a concrete factory if we know which one to use:
auto const blueHouse = buildAHouse(BlueHouseFactory{});
Another option is to centralise the creation of the factory, in a function (that happens to follow another design pattern, Factory Method, which we’ll explore in another post):
std::unique_ptr<HouseFactory> createHouseFactory(Color color) { switch (color) { case Color::Blue: { return std::unique_ptr<BlueHouseFactory>{}; break; } case Color::Red: { return std::unique_ptr<RedHouseFactory>{}; break; } case Color::Yellow: default: { return std::unique_ptr<YellowHouseFactory>{}; break; } } }
This has the drawback of having to deal with the default
case (Should we have a default color as in the above code? Should we produce an error? Or return a nullptr that we test everywhere?).
If you centralise the creation of the factory, know that C++ gives you the option to centralise the definitions of the factories along with them, by using classes nested in a function:
std::unique_ptr<HouseFactory> createHouseFactory(Color color) { switch (color) { case Color::Blue: { class BlueHouseFactory : public HouseFactory { public: std::unique_ptr<Door> createDoor() const override { return std::make_unique<BlueDoor>(); }; std::unique_ptr<Roof> createRoof() const override { return std::make_unique<BlueRoof>(); }; std::unique_ptr<Wall> createWall() const override { return std::make_unique<BlueWall>(); }; }; return std::unique_ptr<BlueHouseFactory>{}; break; } case Color::Red: { class RedHouseFactory : public HouseFactory { public: std::unique_ptr<Door> createDoor() const override { return std::make_unique<RedDoor>(); }; std::unique_ptr<Roof> createRoof() const override { return std::make_unique<RedRoof>(); }; std::unique_ptr<Wall> createWall() const override { return std::make_unique<RedWall>(); }; }; return std::unique_ptr<RedHouseFactory>{}; break; } case Color::Yellow: default: { class YellowHouseFactory : public HouseFactory { public: std::unique_ptr<Door> createDoor() const override { return std::make_unique<YellowDoor>(); }; std::unique_ptr<Roof> createRoof() const override { return std::make_unique<YellowRoof>(); }; std::unique_ptr<Wall> createWall() const override { return std::make_unique<YellowWall>(); }; }; return std::unique_ptr<YellowHouseFactory>{}; break; } } }
An advantage of this is that all the factory code is located together, and you’re guaranteed that no other code can instantiate a BlueHouseFactory
, because they don’t have access to that class.
But as a drawback, it makes the factory creation fonction bigger. If the implementations of the factories are not very simple, this makes the code hard to follow.
Design principles
The role of Abstract Factory is to present an interface for creating objects, that is implemented by various concrete factories. For this reason, I think Abstract Factory implements the Polymorphism GRASP design principle.
Also, the factory often doesn’t model a domain object. It is a technical helper to centralise the creation of consistent objects. This also makes it a Pure Fabrication.
Doesn’t Abstract Factory also implement the Creator GRASP design principle? Given its name, this is what I would have thought before digging in the analysis.
But if we go back to the definition of Creator, it suggests that B should create A if B is close to A: if it uses A, if it has inputs to create A, if it already contains other instances of A, etc. This does not match the description of Abstract Factory as well as Polymorphism does.
Would you also have reacted Abstract Factory to Polymorphism and Pure Fabrication, or to another one?
Do you know of creational patterns that are not in the GoF book?
Let me know by leaving a comment below.
You will also like
- Minor, Major and Overarching Design Principles
- GRASP: 9 Must-Know Design Principles for Code
- esign Patterns VS Design Principles: Chain of responsibility, Command and Interpreter
- Design Patterns VS Design Principles: Iterator, Mediator and Memento
- Design Patterns VS Design Principles: Observer, State and Strategy
Share this post!