Implementing the Simple Factory Pattern in C++ for Arithmetic Operations
The Simple Factory pattern centralizes object instantiation behind a unified interface, adhering strictly to the Dependency Inversion Principle. High-level modules interact exclusively with abstractions rather than concrete implementations, decoupling client logic from construction details.
1. Abstract Base Interface
Defining a common contract ensures that all derived operations share a consistant API. Protected state variables allow subclasses direct access while preventing external mutation.
class BaseCalculator {
public:
virtual ~BaseCalculator() = default;
virtual void assign_operands(double lhs, double rhs) {
left_val_ = lhs;
right_val_ = rhs;
}
virtual double compute() const = 0;
protected:
double left_val_ = 0.0;
double right_val_ = 0.0;
};
2. Concrete Operation Implementations
Each mathematical operation inherits the base contract and provides specific computation logic. The override keyword guarantees signature alignment with the abstract interface.
class AdditionTask : public BaseCalculator {
public:
double compute() const override {
return left_val_ + right_val_;
}
};
class SubtractionTask : public BaseCalculator {
public:
double compute() const override {
return left_val_ - right_val_;
}
};
3. Factory Instantiation Logic
The factory class encapsulates the creation mechanism. By returning a smart pointer, memory management is automated, eliminating manual deallocation and preventing leaks. A conditional structure routes the request to the appropriate concrete class.
#include <memory>
#include <stdexcept>
class CalculatorFactory {
public:
static std::unique_ptr<BaseCalculator> build(char operator_symbol) {
switch (operator_symbol) {
case '+': return std::make_unique<AdditionTask>();
case '-': return std::make_unique<SubtractionTask>();
default: throw std::invalid_argument("Unsupported operator");
}
}
};
4. Client Usage
Consumers request an abstraction from the factory and execute operations without awareness of the underlying concrete type. The lifecycle is automatical handled by the smart pointer.
#include <iostream>
void execute_demo() {
auto calc = CalculatorFactory::build('+');
calc->assign_operands(15.5, 4.5);
std::cout << "Computed Result: " << calc->compute() << '\n';
}
Encapsulating instantiation logic isolates client code from construction details. The factory acts as a decoupling layer, enabling runtime selection of behaviors based on configuration or input parameters.
The primary architectural limitation stems from the conditional branching inside the factory method. Introducing new operations requires modifying the existing switch statement, which directly violates the Open-Closed Principle. Systems requiring frequent extension should consider registration-based factories, polymorphic registries, or template metaprogramming to eliminate conditional modification and achieve true extensibility.