Implementing the Factory Method Pattern in C++
Defining the Product Abstraction
Centralized factory implementations frequently depend on conditional branching to instantiate specific objects based on enput parameters. This approach violates the Open-Closed Principle because introducing new product types mandates modifications to the factory class itself. The Factory Method pattern resolves this architectural limitation by delegating object creation to derived factory classes, ensuring that each concrete product is managed by its own dedicated creator.
class ArithmeticOperation {
public:
virtual ~ArithmeticOperation() = default;
virtual void setOperands(int left, int right) = 0;
virtual int execute() const = 0;
protected:
int operand_a{0};
int operand_b{0};
};
class Addition : public ArithmeticOperation {
public:
void setOperands(int left, int right) override {
operand_a = left;
operand_b = right;
}
int execute() const override { return operand_a + operand_b; }
};
class Subtraction : public ArithmeticOperation {
public:
void setOperands(int left, int right) override {
operand_a = left;
operand_b = right;
}
int execute() const override { return operand_a - operand_b; }
};
Constructing the Creator Hierarchy
The factory interface establishes a common contract for instantiation. Concrete factories implement this contract by returning specific operation instances. This decouples the cleint from concrete product classes and enforces a one-to-one relationship between creators and products.
class OperationFactory {
public:
virtual ~OperationFactory() = default;
virtual ArithmeticOperation* create() = 0;
};
class AdditionFactory : public OperationFactory {
public:
ArithmeticOperation* create() override {
return new Addition();
}
};
class SubtractionFactory : public OperationFactory {
public:
ArithmeticOperation* create() override {
return new Subtraction();
}
};
Client-Side Instantiation
Client code interacts exclusively with the abstract factory and product interfaces. This abstraction layer allows runtime flexibility without coupling implementation details.
void runCalculation() {
OperationFactory* factory_ptr = new AdditionFactory();
ArithmeticOperation* calc_ptr = nullptr;
if (!factory_ptr) return;
calc_ptr = factory_ptr->create();
if (calc_ptr) {
calc_ptr->setOperands(5, 2);
std::cout << "Result: " << calc_ptr->execute() << std::endl;
delete calc_ptr;
}
delete factory_ptr;
}
Extending the Architecture
When expanding the system to support additional operations, such as multiplication, the centralized conditional logic is no longer required. Instead of altering existing factory code, developers simply implement a new product class and pair it with a corresponding factory subclass. This shift reduces modification points from multiple locations down to a single addition. While the system still technically requires new code to introduce features, the elimination of mutation in established modules significantly enhances maintainability and aligns closer to the Open-Closed Principle. The architecture strictly favors extension over modification, isolating changes within newly derived classes rather then dispersing them across conditional branches.