| Lesson 2 | What is a creational pattern? |
| Objective | Define creational patterns and explain why they are used instead of direct object construction. |
In object-oriented systems, you create objects everywhere: services, repositories, controllers, UI widgets, and more.
If every class freely calls new on concrete types, the result is usually tight coupling, duplicated construction
logic, and code that is difficult to change or test.
Creational design patterns address this problem by standardizing how objects are created. Instead of scattering constructors throughout the codebase, you centralize and encapsulate object creation so that the rest of the system depends on stable abstractions rather than on specific classes.
A creational design pattern is a reusable design that:
In the classic “Gang of Four” (GoF) catalog, creational patterns are divided into:
Regardless of the specific pattern, the goal is the same: keep client code focused on what it needs, not how it is constructed.
Calling constructors directly is fine in small examples, but it has several drawbacks in real applications:
new is used.
Creational patterns solve these issues by introducing factories, builders, and prototypes that handle construction in a consistent, centralized way.
Although each pattern has its own structure and tradeoffs, creational patterns share several common characteristics:
new calls, they provide a smaller number of well-defined creation points.
One of the most widely known (and sometimes overused) creational patterns is the Singleton. It ensures that:
The example below shows a simple C++ implementation using a function-local static variable. This approach is thread-safe in modern C++ and avoids manual memory management:
#include <iostream>
class Singleton
{
private:
// Private constructor prevents direct instantiation.
Singleton()
{
std::cout << "Singleton created\n";
}
// Deleted copy operations prevent copying.
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& getInstance()
{
static Singleton instance; // Lazily created on first call, destroyed at program exit.
return instance;
}
void showMessage()
{
std::cout << "Using Singleton instance\n";
}
};
int main()
{
// All calls return the same instance.
Singleton& s1 = Singleton::getInstance();
s1.showMessage();
Singleton& s2 = Singleton::getInstance();
s2.showMessage();
return 0;
}
This example demonstrates a creational pattern in action:
Singleton::getInstance() instead of using new.While Singleton can be useful for shared infrastructure (logging, configuration, etc.), it should be used sparingly. Overuse can introduce hidden dependencies and make tests harder to write. Other creational patterns often provide cleaner and more flexible solutions.
The GoF catalog defines five core creational patterns. The table below summarizes each pattern and the problem it solves.
| Factory Method | Lets a subclass decide which concrete class to instantiate. A client calls a factory method that returns an object from a hierarchy or family of related classes, without knowing the specific type selected. |
| Singleton | Ensures that only one instance of a class exists and provides a controlled access point to that instance. Useful for shared resources such as configuration or logging services. |
| Abstract Factory | Provides an interface for creating families of related or dependent objects without specifying their concrete classes. Commonly used for UI toolkits that must support multiple look-and-feel implementations. |
| Prototype | Creates new objects by cloning existing prototype instances. This is useful when object creation is expensive or when you need to create objects at runtime without specifying exact classes in advance. |
| Builder | Separates the construction of a complex object from its representation. A builder incrementally assembles the object, allowing the same construction process to produce different representations with different builders. |
In practice, creational design patterns are often used together with other patterns. For example, a Builder might be created by an Abstract Factory, or a Factory Method might internally use a Prototype. Regardless of the combination, the central idea remains the same: treat object creation as a first-class design concern rather than a detail scattered across constructors.