Road to C++ Programmer #21 - Creational Design Patterns

Last Edited: 1/22/2025

The blog post introduces creational design patterns in C++.

CPP Creational

Design Patterns

In C++, we have countless options for setting up our classes and objects to solve particular problems in software development. However, this freedom to choose any solution can lead to writing problem-specific code with poorly designed constructors, destructors, inheritance, and composition. Such code often results in reduced readability, maintainability, and scalability.

To address these challenges, software developers have created design patterns—standardized ways of solving common software problems. These patterns help developers write scalable and easily understandable code that others can work with effectively. There are three main types of design patterns: creational, structural, and behavioral design patterns, each serving different purposes. In this article, we will introduce you to some creational design patterns, which aim to make object creation more flexible and efficient.

Factory Method Pattern

The Factory Method Pattern involves creating a IFactory interface that different factory classes can implement. This approach allows all the factory classes to create objects of different classes in a consistent manner. Below is an example of using the Factory Method Pattern to create objects for two different shapes:

// Abstract Class for Shapes
class IShape {
public:
    virtual void draw() = 0;
    virtual ~Shape(){};
};
 
// Abstract Class for Factories
class IShapeFactory {
public:
    virtual Shape* createShape() = 0;
    virtual ~ShapeFactory() {};
};
 
// Shape Circle
class Circle : public IShape {
public:
    void draw() override
    {
        std::cout<<"Drawing a Circle"<<std::endl;
    }
};
 
// Shape Square
class Square : public IShape {
public:
    void draw() override
    {
        std::cout<<"Drawing a Square"<<std::endl;
    }
};
 
// Circle Factory
class CircleFactory : public IShapeFactory {
public:
    Shape* createShape() override { return new Circle(); }
};
 
// Square Factory
class SquareFactory : public IShapeFactory {
public:
    Shape* createShape() override { return new Square(); }
};
 
int main() {
    ShapeFactory* circleFactory = new CircleFactory();
    ShapeFactory* squareFactory = new SquareFactory();
 
    Shape* circle = circleFactory->createShape();
    Shape* square = squareFactory->createShape();
 
    circle->draw(); // => Drawing a Circle
    square->draw(); // => Drawing a Square
 
    delete circleFactory;
    delete squareFactory;
    delete circle;
    delete square;
 
    return 0;
}

By using the IShapeFactory interface, both CircleFactory and SquareFactory can implement the createShape method to create Circle and Square objects, respectively. While the benefits might not be immediately apparent in this example, the Factory Method Pattern is particularly useful in cases where creating an object of a parent class requires multiple child class objects. In such scenarios, the parent object creation logic can be encapsulated within the factory. Additionally, using interfaces like IShapeFactory ensures that adding a new class derived from IShape doesn’t affect other parts of the code. It only involves creating a new concrete factory class derived from IShapeFactory.

Builder Pattern

The Builder Pattern involves creating a Builder class to build an object step-by-step by processing configuration parameters one at a time. This approach simplifies the creation of objects that require many parameters. Here’s an example of how the Builder Pattern can be utilized:

// Pizza class to build
class Pizza {
public:
    string size;
    string sauce;
    string cheese;
    string toppings;
public:
    void setSize(string size) {
        this->size=size;
    };
    void setSauce(string sauce) {
        this->sauce=sauce;
    };
    void setCheese(string cheese) {
        this->cheese=cheese;
    };
    void setToppings(string toppings) {
        this->toppings=toppings;
    };
    void print() {
        cout << "Size: " << this->size << endl;
        cout << "Sauce: " << this->sauce << endl;
        cout << "cheese: " << this->cheese << endl;
        cout << "Toppings: " << this->toppings << endl;
    }
};
 
// Builder for Pizza
class PizzaBuilder {
public:
    Pizza pizza;
public:
    PizzaBuilder &addSize(string size) {
        this->pizza.setSize(size);
        return *this;
    };
    PizzaBuilder &addSauce(string sauce) {
        this->pizza.setSauce(sauce);
        return *this;
    };
    PizzaBuilder &addCheese(string cheese) {
        this->pizza.setCheese(cheese);
        return *this;
    };
    PizzaBuilder &addToppings(string toppings) {
        this->pizza.setToppings(toppings);
        return *this;
    };
    Pizza &build() {
        return this -> pizza;
    };
};
 
int main () {
    PizzaBuilder builder;
    builder.addSize("Medium").addSauce("Tomato");
    Pizza pizza = builder.addCheese("Cheddar").addToppings("Pepperoni").build();
    pizza.print();
    return 0;
};

The builder uses the setter functions of the class to build and configure each attribute, enabling method chaining. More complex logic can also be implemented within each method of the builder if needed. Although the benefits of the Builder Pattern are intuitive, it can sometimes lead to redundant code with numerous simple setter functions and builder methods.

Singleton

There are scenarios where we want to ensure that only one instance of a class is created and shared across the application’s runtime (daemon processes for networking and database). In such cases, the Singleton Pattern can be used.

class Singleton {
private:
    static Singleton* instance;
private:
    Singleton() {
        cout << "Singleton instance created." << endl;
    };
    ~Singleton() {
        cout << "Singleton instance destroyed."<< endl;
    };
public:
    static Singleton& getInstance()
    {
        // If the instance doesn't exist, create it
        if (!instance) {
            instance = new Singleton();
        }
        return *instance;
    };
    void someOperations() {
        cout << "Singleton is performing some operation." << endl;
    };
};
 
// Initialize the static instance variable to nullptr
Singleton* Singleton::instance = nullptr;
 
int main()
{
    // Access the Singleton instance
    Singleton& singleton = Singleton::getInstance();
 
    // Use the Singleton instance
    singleton.someOperations();
 
    // Attempting to create another instance will not work
    // Singleton anotherInstance; // This line would not compile
 
    return 0;
}

This pattern leverages static attributes and methods, which are global variables and shared across all instances and functions that access them. In the getInstance method above, if there is no existing instance of the Singleton class, a new instance is created. Subsequent calls to the method return the same instance. Since the constructor is private and can only be accessed by getInstance, it is made to be impossible to create another instance of Singleton.

Conclusion

In this article, we discussed the concept of design patterns, their usefulness, and examples of creational design patterns. In the next two articles, we will explore examples of structural and behavioral design patterns, which are invaluable for writing and understanding the code of large projects in any object-oriented programming language.

Resources