Template Method
The Strategy pattern provides a way to encapsulate a family of algorithms (JSON processing logic in this case) and make them interchangeable. The Template Method pattern defines the skeleton of an algorithm in a base class, deferring some steps to subclasses. In this example, we refactored a JSON processing system using both patterns for improved flexibility and reduced boilerplate code.
The Problem: Rigidity and Boilerplate
Initially, the JSON processing logic might have been implemented in one of two ways, both with drawbacks:
Hard-Coded Logic: A single class or method contains large
if/else iforswitchstatements to check JSON structures and deserialize accordingly. This violates the Open/Closed Principle (OCP), as modifying the class is required to support new JSON formats.Simple Strategy Implementation: Using the Strategy pattern with a simple interface like
IJsonProcessor, each concrete strategy implements both the check (CanProcess) and the action (Process). This often leads to boilerplate code within theProcessmethod of each strategy, typically checkingif (!CanProcess(json))before proceeding.
This repetition violates the Don't Repeat Yourself (DRY) principle.
The Solution: Combining Strategy and Template Method
We combine the flexibility of Strategy with the boilerplate reduction of the Template Method pattern.
1. Define the Strategy Interface
The core interface defines the contract for any JSON processor, allowing the context class to work with any processor uniformly.
2. Implement the Template Method Base Class
An abstract base class implements IJsonProcessor and uses the Template Method pattern. It defines the overall processing algorithm skeleton, handling the common CanProcess check.
The Process method acts as the template method. Subclasses only need to provide the varying parts: how to check if the JSON is processable (CanProcess) and how to actually process it (ProcessInternal).
3. Implement Concrete Strategies (Subclasses)
Concrete strategies now inherit from BaseJsonProcessor<T> and provide specific implementations for the abstract methods, free from the repetitive check.
4. Context Class Using the Strategies
The context class (e.g., JsonController) depends on the IEnumerable<IJsonProcessor> abstraction. It iterates through the available processors (strategies) and uses the first one that successfully processes the JSON (returns a non-null result). It doesn't need to know about the BaseJsonProcessor or the template method.
5. Register Strategies with Dependency Injection
Register the concrete processor types against the IJsonProcessor interface in the DI container. The container provides all registered implementations when IEnumerable<IJsonProcessor> is requested.
Benefits
Open/Closed Principle (OCP): The system is open for extension (add new
BaseJsonProcessor<T>subclasses and register them) but closed for modification (theJsonControllerandBaseJsonProcessorlogic remain unchanged).Single Responsibility Principle (SRP): Each concrete processor focuses solely on handling its specific JSON structure. The base class handles the common workflow.
Don't Repeat Yourself (DRY): The Template Method in
BaseJsonProcessoreliminates the repetitiveCanProcesscheck from concrete strategies.Flexibility & Maintainability: Easily add, remove, or modify JSON processing strategies without impacting unrelated code. Concrete strategies are simpler and easier to understand.
See Also: