Introduction to Chain of Responsibility
Chain of Responsibility passes a request along a chain of handlers. Each handler decides either to process the request or to pass it to the next handler.
The Problem
Complex validation with many conditions:
typescript1// Monolithic validation function2function validateAddress(address: Address): ValidationResult {3 const errors: string[] = [];45 // Format validation6 if (!address.street) errors.push('Street required');7 if (!address.city) errors.push('City required');8 if (!address.postalCode) errors.push('Postal code required');910 // Postal code format11 if (address.country === 'US' && !/^\d{5}(-\d{4})?$/.test(address.postalCode)) {12 errors.push('Invalid US postal code');13 }1415 // Geocoding check16 const coords = geocode(address);17 if (!coords) errors.push('Address could not be geocoded');1819 // Deliverability check20 const deliverable = checkDeliverability(address);21 if (!deliverable) errors.push('Address not deliverable');2223 // ... many more checks2425 return { valid: errors.length === 0, errors };26}2728// Problems:29// - Hard to add/remove validations30// - Can't reuse individual checks31// - All checks run even if early ones fail32// - Testing is difficult
The Solution: Chain of Responsibility
Create a chain of handlers:
typescript1interface AddressHandler {2 setNext(handler: AddressHandler): AddressHandler;3 handle(address: Address): ValidationResult;4}56abstract class AbstractHandler implements AddressHandler {7 private nextHandler: AddressHandler | null = null;89 setNext(handler: AddressHandler): AddressHandler {10 this.nextHandler = handler;11 return handler;12 }1314 handle(address: Address): ValidationResult {15 if (this.nextHandler) {16 return this.nextHandler.handle(address);17 }18 return { valid: true, errors: [] };19 }20}2122class FormatValidator extends AbstractHandler {23 handle(address: Address): ValidationResult {24 const errors: string[] = [];2526 if (!address.street) errors.push('Street required');27 if (!address.city) errors.push('City required');28 if (!address.postalCode) errors.push('Postal code required');2930 if (errors.length > 0) {31 return { valid: false, errors };32 }3334 return super.handle(address); // Pass to next35 }36}3738class PostalCodeValidator extends AbstractHandler {39 handle(address: Address): ValidationResult {40 if (address.country === 'US' && !/^\d{5}(-\d{4})?$/.test(address.postalCode)) {41 return { valid: false, errors: ['Invalid US postal code'] };42 }43 return super.handle(address);44 }45}4647class GeocodingValidator extends AbstractHandler {48 handle(address: Address): ValidationResult {49 const coords = this.geocode(address);50 if (!coords) {51 return { valid: false, errors: ['Cannot geocode address'] };52 }53 return super.handle(address);54 }55}
Building the Chain
typescript1const formatValidator = new FormatValidator();2const postalValidator = new PostalCodeValidator();3const geocodingValidator = new GeocodingValidator();4const deliverabilityValidator = new DeliverabilityValidator();56// Chain them together7formatValidator8 .setNext(postalValidator)9 .setNext(geocodingValidator)10 .setNext(deliverabilityValidator);1112// Use the chain13const result = formatValidator.handle(address);
Pattern Structure
1┌──────────┐ ┌─────────────────────────────────┐2│ Client │────►│ Handler │3└──────────┘ │ (interface) │4 │─────────────────────────────────│5 │ + setNext(handler): Handler │6 │ + handle(request): Result │7 └─────────────────────────────────┘8 ▲9 ┌────────────────────┼────────────────────┐10 │ │ │11┌────────┴────────┐ ┌────────┴────────┐ ┌────────┴────────┐12│ HandlerA │ │ HandlerB │ │ HandlerC │13│─────────────────│ │─────────────────│ │─────────────────│14│ + handle() │ │ + handle() │ │ + handle() │15└─────────────────┘ └─────────────────┘ └─────────────────┘
When to Use Chain of Responsibility
- Multiple handlers - More than one object may handle a request
- Dynamic handlers - Set of handlers determined at runtime
- Sequential processing - Handlers should be tried in order
- Decoupling - Sender shouldn't know which handler will process
Logistics Use Cases
| Chain | Handlers |
|---|---|
| Address Validation | Format → Postal → Geocoding → Deliverability |
| Order Approval | Clerk → Manager → Director |
| Discount Calculation | Loyalty → Volume → Promotional → Seasonal |
| Package Routing | Local → Regional → National → International |
Summary
Chain of Responsibility decouples senders from receivers, allowing multiple handlers to process a request. Each handler can either handle the request or pass it along.