Addressing Extensibility with the Factory Method
Software systems requiring future extensibility must adhere to the Open-Closed Principle. When a module needs to perform operations without knowing the exact implementation details upfront, object creation must be decoupled from the core logic. The Factory Method pattern resolves this by deferring instantiation to subclasses.
Consider a task execution framework where the specific operations might evolve over time. The framework must trigger a process while remaining completely decoupled from the exact task implementation. We first define a generic contract for these operations.
public interface TaskHandler {
void execute();
} The execution framework cannot be tightly bound to any specific TaskHandler. Instead, it provides an abstract structure for running tasks, delegating the instantiation logic to its subclasses. This guarantees the core engine remains untouched when new task types are introduced.
public abstract class ExecutionEngine {
public void runTask() {
TaskHandler handler = createHandler();
handler.execute();
}
protected abstract TaskHandler createHandler();
} Concrete operations implement the TaskHandler interface, providing the necessary execution logic.
public class DataExportHandler implements TaskHandler {
@Override
public void execute() {
System.out.println("Executing data export process...");
}
}
public class ReportGenerationHandler implements TaskHandler {
@Override
public void execute() {
System.out.println("Generating system reports...");
}
} To connect the engines with their respective handlers, specialized engine subclasses override the creation hook, supplying the appropriate handler instance.
public class ExportEngine extends ExecutionEngine {
@Override
protected TaskHandler createHandler() {
return new DataExportHandler();
}
}
public class ReportEngine extends ExecutionEngine {
@Override
protected TaskHandler createHandler() {
return new ReportGenerationHandler();
}
} Deploying the framework requires selecting the appropriate engine variant based on the required operation.
public class Application {
public static void main(String[] args) {
ExecutionEngine exportRunner = new ExportEngine();
exportRunner.runTask();
ExecutionEngine reportRunner = new ReportEngine();
reportRunner.runTask();
}
} Pattern Analysis and Implications
This structure embodies the Factory Method pattern. The core definition specifies that an interface declares the object creation method, while concrete subclasses perform the actual instantiation. By abstracting the factory logic, the system introduces new product types by adding new factory subclasses rather than altering existing core logic.
Deferring instantiation to subclasses enables a parent class to execute its operations independently of the specific objects it manipulates. This dual abstraction—applied to both the product and the creator—guarantees that extensions require only new implementations, perfectly aligning with the Open-Closed Principle. The Factory Method pattern evolved from the Simple Factory approach; removing the abstract creator and relying on conditional logic within a single factory class reverts the design to a Simple Factory.
Practical implementations offer flexibility. A single concrete creator might provide multiple factory methods for closely related products, or group similar instantiations together. While this resembles a Simple Factory structurally, the foundational principle remains intact: object creation is deferred to subclasses, isolating extension points from existing code.
However, this pattern introduces class proliferation. Each new product necessitates a corresponding creator subclass, increasing system complexity. Applying this pattern in inappropriate scenarios where product variations are limited can over-engineer the solution.