Factory Method in C++, the Right Way

Ant Wang
10 min readAug 21, 2023

--

Photo by Alex Simpson on Unsplash

One of the first design patterns you will encounter, and maybe you have already implemented it without knowing, is the factory method pattern. As I am recently going through Refactoring Guru to pay off my code debt as a self-taught software engineer, I found the idea of factory method particularly hard to grasp. A seemingly simple and elementary pattern took me longer than expected to understand and apply. I decided to share in this article what I learned and the things that confused me while learning the factory design pattern in C++.

There are a lot of different definitions and variations of factory method pattern, and there isn’t a right or wrong answer. Ultimately, design pattern is not a standard; it’s a practice. But for this article, I will stick with the definition found on Refactoring Guru:

Factory Method is a creational design pattern that provides an interface for creating objects in a superclass, but allows subclasses to alter the type of objects that will be created.

Now that we have a ground truth on what the definition is, here are some of the points I found confusing while learning this pattern.

  • The various interpretations of factory method
  • The UML diagram on creator and product.

After that I will share the lessons learned:

  • The SOP to implement factory method
  • Factory method is not limited to object creation

Various Interpretations of Factory Method

Frankly, when I first read through the many pages of definition on Factory Method on Refactoring Guru (link here) it was overwhelming. Everything seemed to make sense but when I finished reading it, I asked myself, “how do you implement and apply that to your code”, I was lost. Naturally, I started to go onto different websites and seek for more examples. But that only confused me even further.

Some people implement factory method simply as a function that takes care of the creation of class objects while abstracting the implementation details, and some people define the factory method as a helper class that merely simplifies the creation. I’ll use an example to illustrate what I mean.

Suppose you have a Battleship superclass that extends into different types of subclasses like Destroyer, Carrier… etc.

class Battleship {
public:
Battleship() {
std::cout << "Battleship Created" << std::endl;
}
virtual void Fire() = 0;
virtual void Steer() = 0;
};

class Destroyer : public Battleship {
public:
Destroyer(){
std::cout << "Destroyer Created" << std::endl;
}
void Fire() override {
std::cout << "Destoryer Fire" << std::endl;
}
void Steer() override {
std::cout << "Destroyer Steer" << std::endl;
}
};

class Carrier : public Battleship {
public:
Carrier(){
std::cout << "Carrier Created" << std::endl;
}
void Fire() override {
std::cout << "Carrier Fire" << std::endl;
}
void Steer() override {
std::cout << "Carrier Steer" << std::endl;
}
};

You want to create instances of each subclass and the most primitive way would be using using the correct class type:

int main() {
Destroyer d;
Carrier c;
d.Fire();
c.Fire();
return 0;
}

Now, this is where the core principle of Factory Method comes in, to simplify the creation process so the user, or client code, doesn’t need to know which subclass type to use or worry about the implementation details of the creation process.

On one of the tutorials I found online, the factory method pattern is narrowed down to the concept of using a function to encapsulate the object creation process. The factory method would be implemented like this:

enum class ShipType{
Destroyer,
Carrier,
};

Battleship* CreateShipFactory(ShipType type){
Battleship* pShip = nullptr;
if(type == ShipType::Carrier) {
pShip = new Carrier();
}
else if(type == ShipType::Destroyer) {
pShip = new Destroyer();
}
return pShip;
}

Of course, you can use other ways to differentiate what type of ship to create such as passing in a std::string (“Carrier”, “Destroyer”) or a int (1 means Carrier, 2 means Destroyer) but why not leverage the power of scoped enum in C++? With this implementation, we can now not worry about the object creation process, and the client code or user simply needs to call the wrapper function, CreateShipFactory with the desired parameter like such:

int main() {
Battleship* d = CreateShipFactory(ShipType::Destroyer);
Battleship* c = CreateShipFactory(ShipType::Carrier);
d->Fire();
c->Fire();
return 0;
}

I agree that this captures the essence of factory method, but this seems like an oversimplified version. So I continued looking and I found the definition on Neetcode (link) where you can create a factory class that instantiates the objects for us without worrying about the list of parameters that needs to be passed into the constructor for object creation. This class would look like this:

class CreateShipFactory{
public:
Battleship* CreateCarrier(){
return new Carrier(param1, param2, param3...);
}
Battleship* CreateDestroyer(){
return new Destroyer(param1, param2, param3...);
}
};

In the client code, the user simply needs to instantiate a factory object and called the factory methods to return objects.

int main() {
CreateShipFactory factory;
BattleShip* d = factory.CreateDestroyer();
BattleShip* c = factory.CreateCarrier();
return 0;
}

The user doesn’t need to even consider what parameters are passed into the constructor of these classes. They simply ask the factory to return a cookie-cut object with pre-defined parameters.

Just by looking at these two examples, we can already see the interpretation of factory pattern varies from person to person. Compared with the example given in Refactoring Guru, which I will get into shortly, these two examples seem a lot more simple and it’s tempting at this point to just call it a day and say I’ve learned the factory method. But we cannot give in to our nature of taking shortcut but instead insist on the highest standard. Quoting Refactoring Guru, the reason why we want to learn design pattern is because it “define(s) a common language that you (…) can use to communicate more efficiently.”

Personally, I think the issue with the first approach is that it’s merely a wrapper function that takes care of object creation and abstracts away the object creation process. The issue with the second approach, while there is a “Creator class” that starts to align with the definition by Refactoring Guru, it’s still oversimplifying the pattern. Forcing the client to use a standardized parameter when constructing objects does not seem to be the idea of factory method, more so, cookie cutting technique of object creation.

The UML Diagram on Creator and Product

To fully understand the definition of factory method on Refactoring Guru, we need to look at the UML Diagram provided on the site.

Screenshot taken from Refactoring Guru — Dive into Design Patterns

This UML summarizes the gist of Factory Method, so let’s fully understand what it means. Product means the superclass, or interface, the objects we ought to create implements. Concrete Products are the actual objects we ought to create. In our previous example, BattleShip class is the Product, and Destroyer and Carrier are the Concrete Products.

Creator is the “Factory Class” which extends into Concrete Creators which will create the Concrete Products for us. Without revealing too much information coming up, just know that there will be a Concrete Creator — DestroyerCreator that creates Destroyer’s, and similarly a CarrierCreator that creates Carrier’s.

The dashed simple arrow linking Creator to Product means that Creator depends on Product Interface. That makes sense, because the job of Creator is to create Product’s so naturally the dependency is self-evidence.

The dashed triangle arrow (- -▷)means that Concrete Products implements the interface Product whereas the solid triangle arrow ( — ▷) means that Concrete Creators inherits from Creator. The difference between the two is subtle and is not relevance in this article so you can treat the two as the same, just child class inheriting from parent class.

One thing that isn’t listed in the diagram is important to keep in mind for now — a virtual function from Creator needs to be overridden in the Concrete Creators.

So there you have it, the three elements of the factory method pattern:

  • Products Interface and Concrete Products
  • Creator and Concrete Creators for creating products
  • Virtual function in Creator that gets overridden in Concrete Creators

The outcome of using this design pattern is that the user or the client code, without worrying about the implementation details, or type of function to call, as long as the correct concrete creator is chosen, a correct concrete product will be returned. Let’s see this in practice.

To implement Factory Method pattern, Refactoring Guru provide a step by step SOP to follow. Here, I will implement it on the Battleship example following the SOP.

Step 1: Make all products follow the same interface. This interface should declare methods that make sense in every product.

Looks like we already have done this, the Battleship interface is used to implement the classes Destroyer and Carrier. But let’s just write it out again for continuity sake.

class Battleship {
public:
Battleship() {
std::cout << "Battleship Created" << std::endl;
}
virtual void Fire() = 0;
virtual void Steer() = 0;
};

class Destroyer : public Battleship {
public:
Destroyer(){
std::cout << "Destroyer Created" << std::endl;
}
void Fire() override {
std::cout << "Destoryer Fire" << std::endl;
}
void Steer() override {
std::cout << "Destroyer Steer" << std::endl;
}
};

class Carrier : public Battleship {
public:
Carrier(){
std::cout << "Carrier Created" << std::endl;
}
void Fire() override {
std::cout << "Carrier Fire" << std::endl;
}
void Steer() override {
std::cout << "Carrier Steer" << std::endl;
}
};

Step 2. Add an empty factory method inside the creator class. The return type of the method should match the common product interface.

class ShipCreator {
public:
Battleship* FactoryMethod() {};
};

Empty method, FactoryMethod with return type BattleShip* which matches the common product interface.

Step 3. In the creator’s code find all references to product constructors. One by one, replace them with calls to the factory method, while extracting the product creation code into the factory method.

You might need to add a temporary parameter to the factory method to control the type of returned product.

This is where we get to the if-else or switch case statement. We want to temporarily use logic, instead of leveraging the power of OOP, to choose the right concrete product to create. Our temporary parameter is ShipType which will be passed in by the user or client code.

class ShipCreator {
public:
Battleship* FactoryMethod() {
if(type==ShipType::Carrier) {
return new Carrier();
}
else if(type==ShipType::Destroyer) {
return new Destroyer();
} else {
return nullptr;
}
};

void CreateShip(ShipType type) {
this->type = type;
Battleship* ptr = this->FactoryMethod();
}

ShipType type;
};

At this point, we already have a functioning factory method implementation that the client code can benefit from. To create concrete products, the client code would simply need to call CreateShip() with the desired ShipType like so:

int main() {
ShipCreator creator;
creator.CreateShip(ShipType::Carrier);
creator.CreateShip(ShipType::Destroyer);
return 0;
}

/** Output Expected:
Battleship Created
Carrier Created
Battleship Created
Destroyer Created
**/

Step 4. Now, create a set of creator subclasses for each type of product listed in the factory method. Override the factory method in the subclasses and extract the appropriate bits of construction code from the base method.

In other words, we are replacing the ugly if-else statement by leveraging the power of run-time polymorphism.

Our Creator superclass and Concrete Creator subclasses now look like this:

class ShipCreator {
public:
virtual Battleship* FactoryMethod() = 0;

void CreateShip() {
Battleship* ptr = this->FactoryMethod();
}
};

class CarrierCreator : public ShipCreator {
Battleship* FactoryMethod() override {
return new Carrier();
}
};

class DestroyerCreator : public ShipCreator {
Battleship* FactoryMethod() override {
return new Destroyer();
}
};

Essentially, CreateShip() is extended into the subclasses, so calling the CreateShip() in CarrierCreator will invoke the FactoryMethod in that returns a new Carrier().

At this point, we have successfully implemented a Factory Method. For the client code, all we need to do to instantiate objects would be:

int main() {
ShipCreator* creator = new CarrierCreator();
creator->CreateShip();
creator = new DestroyerCreator();
creator->CreateShip();
return 0;
}
/** Output Expected:
Battleship Created
Carrier Created
Battleship Created
Destroyer Created
**/

Steps 5 and 6 of the SOP is basically optional that states that you can make your Creator class abstract which I did in the example by declaring FactoryMethod() to be a pure virtual function and how to use control parameters when the class hierarchy is more complex. We don’t need to get into those for now.

At this point, I’m hoping you see the cleanliness of the code that follows the more complicated definition of Factory Method Design Pattern as defined in Refactoring Guru. It’s is much stricter, and consists of additional Creator classes and runtime polymorphism, but, in my opinion, this is the right way to do it. Let’s compare again the client code in the three different examples.

// No Design Pattern Implemented
int main() {
Destroyer d;
Carrier c;
return 0;
}

// Factory "Function" Implemented
int main() {
Battleship* d = CreateShipFactory(ShipType::Destroyer);
Battleship* c = CreateShipFactory(ShipType::Carrier);
return 0;
}

// Factory "Class" Implemented
int main() {
CreateShipFactory factory;
BattleShip* d = factory.CreateDestroyer();
BattleShip* c = factory.CreateCarrier();
return 0;
}

// Factory Method Design Pattern Implemented
int main() {
ShipCreator* creator = new CarrierCreator();
creator->CreateShip();
creator = new DestroyerCreator();
creator->CreateShip();
return 0;
}

The Factory “Function” is merely a wrapper function that facilitates the object creation. The Factory “Class” is a helper class that still requires the user to choose which kind of method to use for creating the right concrete product. The Factory “Method” leverages function overriding, and as long as the user selects the correct Concrete Creator, the process the create objects are all the same.

At the end of the day, all the above are achieving similar purpose in different means, but the proper Factory Method implementation is more elegant, less prone to error, and scalable.

One thing to note that while the classes are called Creator classes and the Factory Method is a “Creational” Design Pattern, but this doesn’t limit the factory method to be solely for object creation. We can of course expand the concrete creator class for other type of functionalities that still falls into the pattern of factory method.

--

--

Ant Wang

Self taught software developer passionate for robotics and industrial automation.