The Factory Method
The classic book “Design Patterns: Elements of Reusable Object-Oriented Software” has 23 patterns. They are ordered by intent: creational, structural, and behavioral patterns. Today, I focus on the creational pattern Factory Method.
Five patterns in the book “Design Patterns: Elements of Reusable Object-Oriented Software” (short Design Patterns) are creational, seven structural, and the remaining one behavioral. First of all, what does this mean?
- Creational patterns deal with object creation in a well-defined way.
- Structural patterns provide mechanisms to organize class and objects for larger structures.
- Behavioral patterns deal with communication patterns between objects.
Before I start with the creational patterns, I want to make a short disclaimer.
Short Disclaimer
I present about half of the 23 design patterns. For the remaining ones, I only provide a fact sheet. The selection of the presented design pattern is based on two points.
- Which patterns did I encounter most often as a software developer in the last twenty years?
- Which patterns are still in use?
My explanation of the presented design patterns is intentionally concise. My idea is to present the key principle of a pattern and present them from a C++ point of view. If you want to have more details, there is excellent documentation available. Here are a few choices:
- The classic: “Design Patterns: Elements of Reusable Object-Oriented Software“
- Nice introduction: “Head First Design Patterns”
- Wikipedia articles: Design Patterns
Creational patterns deal with object creation. Let’s dive deeper.
Creational Patterns
I will write about two of the five creational patterns: Factory Method and Singleton. I know, I know the Singleton could also be regarded as an Anti-Pattern. In a later post, I will discuss the Singleton in more depth. Let’s start with the Factory Method:
Modernes C++ Mentoring
Do you want to stay informed: Subscribe.
Factory Method
Here are the facts:
Purpose
The factory method defines an interface to create a single object but lets subclasses decide which objects to create. The interface can provide a default implementation for creating objects.
Also known as
Virtual constructor
Use Case
- A class does not know what kind of objects to create
- Subclasses decide what object to create
- Classes delegate the creation of objects to subclasses
Example
Each container of the Standard Template Library has eight factory functions to generate various iterators.
begin
,cbegin
: returns an iterator to the beginning of the containerend, cend
: returns an iterator to the end of the containerrbegin, crbegin
: returns a reverse iterator to the beginning of the containerrend, crend
: returns a reverse iterator to the end of the container
c,
return constant iterators.Structure
Product
- Objects created by
factoryMethod.
Concrete Product
- Implements the interface
Creator
- Declares the factory method
- Calls the factory method
Concrete Creator
- Overwrites the factory method
The Creator does not instantiate the Concrete Product. It calls its virtual member function factoryMethod
. Consequentially, the Concrete Product is created by the Concrete Creator, and the object creation is independent of the Creator.
This pattern is also known as a virtual constructor.
Related Patterns
- Abstract Factory is typically implemented using Factory Methods.
- Factory Methods are often steps of the Template Method.
Virtual Constructor
Honestly, the name virtual constructor is misleading. We don’t have a virtual constructor in C++, but we have virtual construction to simulate a virtual constructor.
Assume you have a class hierarchy with an interface class Window
and two implementation classes, DefaultWindow
and FancyWindow.
// Product class Window { public: virtual ~Window() {}; }; // Concrete Products class DefaultWindow: public Window {}; class FancyWindow: public Window {};
Now, you want to create a new Window
based on an existing one. This means that when you put an instance of
DefaultWindow
or FancyWindow
inside the factory function getNewWindow
, it should return
an instance of the same class.
Classically, the factory method is implemented using an enum
and a factory function. Here is the first try:
// factoryMethodClassic.cpp #include <iostream> enum class WindowType { // (5) DefaultWindow, FancyWindow }; // Product class Window { public: virtual ~Window() {}; virtual WindowType getType() const = 0; virtual std::string getName() const = 0; }; // Concrete Products class DefaultWindow: public Window { public: WindowType getType() const override { return WindowType::DefaultWindow; } std::string getName() const override { return "DefaultWindow"; } }; class FancyWindow: public Window { public: WindowType getType() const override { return WindowType::FancyWindow; } std::string getName() const override { return "FancyWindow"; } }; // Concrete Creator or Client Window* getNewWindow(Window* window) { // (1) switch(window->getType()){ // (4) case WindowType::DefaultWindow: return new DefaultWindow(); break; case WindowType::FancyWindow: return new FancyWindow(); break; } return nullptr; } int main() { std::cout << '\n'; DefaultWindow defaultWindow; FancyWindow fancyWindow; const Window* defaultWindow1 = getNewWindow(&defaultWindow); // (2) const Window* fancyWindow1 = getNewWindow(&fancyWindow); // (3) std::cout << defaultWindow1->getName() << '\n'; std::cout << fancyWindow1->getName() << '\n'; delete defaultWindow1; delete fancyWindow1; std::cout << '\n'; }
The factory function in line (1) decides, based on the incoming Window
, which Window
(lines 2 and 3) should be created. It uses window->getType() (line 4) to get the right Window
type. The WindowType
is an enumeration.
Finally, here is the output of the program:
Honestly, I don’t like this solution for the following reasons:
- When my application should support, I would have to extend the enumeration
WindowType
and the switch statement. - The switch statement becomes more and more difficult to maintain if I add new
WindowType
‘s. - The code is too complicated. This is mainly due to the
switch
statement.
Let me replace the switch statement with a virtual dispatch. Additionally, I also want to clone the existing Window
‘s.
// factoryMethod.cpp #include <iostream> // Product class Window{ public: virtual Window* create() = 0; // (1) virtual Window* clone() = 0; // (2) virtual ~Window() {}; }; // Concrete Products class DefaultWindow: public Window { DefaultWindow* create() override { std::cout << "Create DefaultWindow" << '\n'; return new DefaultWindow(); } DefaultWindow* clone() override { std::cout << "Clone DefaultWindow" << '\n'; return new DefaultWindow(*this); } }; class FancyWindow: public Window { FancyWindow* create() override { std::cout << "Create FancyWindow" << '\n'; return new FancyWindow(); } FancyWindow* clone() override { std::cout << "Clone FancyWindow" << '\n'; return new FancyWindow(*this); // (5) } }; // Concrete Creator or Client Window* createWindow(Window& oldWindow) { // (3) return oldWindow.create(); } Window* cloneWindow(Window& oldWindow) { // (4) return oldWindow.clone(); } int main() { std::cout << '\n'; DefaultWindow defaultWindow; FancyWindow fancyWindow; const Window* defaultWindow1 = createWindow(defaultWindow); const Window* fancyWindow1 = createWindow(fancyWindow); const Window* defaultWindow2 = cloneWindow(defaultWindow); const Window* fancyWindow2 = cloneWindow(fancyWindow); delete defaultWindow1; delete fancyWindow1; delete defaultWindow2; delete fancyWindow2; std::cout << '\n'; }
Window supports now two ways to create new ones: a default constructed Window
with the member function create
(line 1) and a copy constructed Window
with the member function clone
(line 2). The subtle difference is that the constructor takes the this pointer in the member function clone
. (line The factory functions createWindow
(line 3) and cloneWindow
(line 4) dispatch on the dynamic type.
The output of the program is promising. Both member functions create
and clone
display the name of the object they create.
By the way. It is fine that the virtual member functions create
and clone
member functions of the DefaultWindow
and the FancyWindow
are private
, because you use them via the Window
interface. In the interface, both member function are
public
.
What’s Next?
Am I’m done with the factory method? NO! The program factoryMethod.cpp
has two serious issues. ownership semantic and slicing. Let me write more about it in my next post.
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery,and Matt Godbolt.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
My special thanks to Embarcadero | |
My special thanks to PVS-Studio | |
My special thanks to Tipi.build | |
My special thanks to Take Up Code | |
My special thanks to SHAVEDYAKS |
Modernes C++ GmbH
Modernes C++ Mentoring (English)
Rainer Grimm
Yalovastraße 20
72108 Rottenburg
Mail: schulung@ModernesCpp.de
Mentoring: www.ModernesCpp.org
Modernes C++ Mentoring,
Leave a Reply
Want to join the discussion?Feel free to contribute!