Core Components of the Strategy Pattern
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Its standard implementation includes three key elements:
- Context Class (Context): Holds a rfeerence to a strategy object, often delegating the algorithm's execution to it.
- Strategy Interface (Strategy): A common interface that all concrete algorithm implementations must adhere to.
- Concrete Strategy Classes: Individual classes that implement the Strategy interface, each providing a specific variant of the algorithm.
Benefits and Drawbacks
Primary Advantages:
- Enhanced Extensibility: Introducing a new algorithm requires only adding a new concrete strategy class without modifying existing code.
- Separation of Concerns: Each algorithm is isolated within its own class, promoting single responsibility and simplifying maintenance.
Potential Limitations:
- Class Proliferation: The number of classes can increase significantly when a large number of strategies are required.
Practical Application: Generating Carrier-Specific Excel Files
Consider a scenario where a system must generate Excel reports tailored for three different telecom carriers (e.g., CarrierA, CarrierB, CarrierC). The content and format of these reports vary per carrier.
Project Structure
src/main/java/com/example/report/
├── controller/
│ └── ReportController.java
├── service/
│ ├── ReportService.java
│ └── impl/
│ └── ReportServiceImpl.java
└── strategy/
├── ExcelGenerator.java // Strategy Interface
├── ReportGeneratorContext.java // Context
└── impl/
├── CarrierAExcelGenerator.java
├── CarrierBExcelGenerator.java
└── CarrierCExcelGenerator.java
Strategy Interface (ExcelGenerator)
Defines the contract for all Excel generation algorithms.
public interface ExcelGenerator {
Workbook generateWorkbook(String dataSourceId) throws BusinessException;
}
Context Class (ReportGeneratorContext)
Manages the available strategies. It uses Spring's dependency injection to automatically collect all beans implementing ExcelGenerator, keyed by their Spring bean names.
@Component
public class ReportGeneratorContext {
private final Map<String, ExcelGenerator> generatorRegistry = new ConcurrentHashMap<>();
@Autowired
public ReportGeneratorContext(Map<String, ExcelGenerator> strategies) {
// Strategies map key is the Spring bean name (e.g., "carrierAExcelGenerator")
this.generatorRegistry.putAll(strategies);
}
public Workbook executeGeneration(String carrierCode, String dataSourceId) throws BusinessException {
ExcelGenerator selectedGenerator = generatorRegistry.get(carrierCode);
if (selectedGenerator == null) {
throw new BusinessException("ERR_CARRIER_NOT_SUPPORTED", "Unsupported carrier code: " + carrierCode);
}
return selectedGenerator.generateWorkbook(dataSourceId);
}
}
Concrete Strategy Implementations
Each class implements the generation logic for a specific carrier.
Example for Carrier A:
@Service("carrierA") // Bean name acts as the strategy key
public class CarrierAExcelGenerator implements ExcelGenerator {
private static final Logger log = LoggerFactory.getLogger(CarrierAExcelGenerator.class);
@Override
public Workbook generateWorkbook(String dataSourceId) throws BusinessException {
log.info("Generating Carrier A report for ID: {}", dataSourceId);
// Specific logic to create Carrier A's Excel format
Workbook wb = new XSSFWorkbook();
// ... populate sheets, rows, cells for Carrier A ...
return wb;
}
}
Examples for Carrier B and Carrier C would follow the same pattern, with unique bean names (e.g., "carrierB", "carrierC") and their own internal logic.
Service Layer Integration (ReportServiceImpl)
The service orchestrates the generation process by utilizing the context.
@Service
public class ReportServiceImpl implements ReportService {
private static final Logger log = LoggerFactory.getLogger(ReportServiceImpl.class);
@Autowired
private ReportGeneratorContext reportContext;
@Override
public void exportReports(String dataSourceId, HttpServletResponse servletResponse) throws BusinessException {
try {
// Delegate generation to the appropriate strategies via the context
Workbook reportForA = reportContext.executeGeneration("carrierA", dataSourceId);
Workbook reportForB = reportContext.executeGeneration("carrierB", dataSourceId);
Workbook reportForC = reportContext.executeGeneration("carrierC", dataSourceId);
// Subsequent logic: write workbooks to response, combine files, etc.
// ...
} catch (BusinessException ex) {
log.error("Report generation failed", ex);
throw ex;
}
}
}
Controller Endpoint (ReportController)
A REST controller exposes the functionality.
@RestController
@RequestMapping("/api/reports")
public class ReportController {
private static final Logger log = LoggerFactory.getLogger(ReportController.class);
@Autowired
private ReportService reportService;
@PostMapping("/export")
public ResponseEntity<?> triggerExport(@RequestParam("sourceId") String sourceId,
HttpServletRequest request,
HttpServletResponse response) {
try {
log.info("Export request for source: {}", sourceId);
reportService.exportReports(sourceId, response);
return ResponseEntity.ok().build();
} catch (BusinessException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
.body(new ErrorResponse(e.getCode(), e.getMessage()));
}
}
}
This approach cleanly separates the varying Excel generation logic for each carrier into distinct strategy classes. The ReportGeneratorContext serves as a central dispatcher, selecting and executing the correct strategy based on a simple identifier. New carriers can be supported by adding a new implementation of ExcelGenerator without altering the existing service or context code.