20 minlesson

Introduction to Chain of Responsibility

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:

typescript
1// Monolithic validation function
2function validateAddress(address: Address): ValidationResult {
3 const errors: string[] = [];
4
5 // Format validation
6 if (!address.street) errors.push('Street required');
7 if (!address.city) errors.push('City required');
8 if (!address.postalCode) errors.push('Postal code required');
9
10 // Postal code format
11 if (address.country === 'US' && !/^\d{5}(-\d{4})?$/.test(address.postalCode)) {
12 errors.push('Invalid US postal code');
13 }
14
15 // Geocoding check
16 const coords = geocode(address);
17 if (!coords) errors.push('Address could not be geocoded');
18
19 // Deliverability check
20 const deliverable = checkDeliverability(address);
21 if (!deliverable) errors.push('Address not deliverable');
22
23 // ... many more checks
24
25 return { valid: errors.length === 0, errors };
26}
27
28// Problems:
29// - Hard to add/remove validations
30// - Can't reuse individual checks
31// - All checks run even if early ones fail
32// - Testing is difficult

The Solution: Chain of Responsibility

Create a chain of handlers:

typescript
1interface AddressHandler {
2 setNext(handler: AddressHandler): AddressHandler;
3 handle(address: Address): ValidationResult;
4}
5
6abstract class AbstractHandler implements AddressHandler {
7 private nextHandler: AddressHandler | null = null;
8
9 setNext(handler: AddressHandler): AddressHandler {
10 this.nextHandler = handler;
11 return handler;
12 }
13
14 handle(address: Address): ValidationResult {
15 if (this.nextHandler) {
16 return this.nextHandler.handle(address);
17 }
18 return { valid: true, errors: [] };
19 }
20}
21
22class FormatValidator extends AbstractHandler {
23 handle(address: Address): ValidationResult {
24 const errors: string[] = [];
25
26 if (!address.street) errors.push('Street required');
27 if (!address.city) errors.push('City required');
28 if (!address.postalCode) errors.push('Postal code required');
29
30 if (errors.length > 0) {
31 return { valid: false, errors };
32 }
33
34 return super.handle(address); // Pass to next
35 }
36}
37
38class 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}
46
47class 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

typescript
1const formatValidator = new FormatValidator();
2const postalValidator = new PostalCodeValidator();
3const geocodingValidator = new GeocodingValidator();
4const deliverabilityValidator = new DeliverabilityValidator();
5
6// Chain them together
7formatValidator
8 .setNext(postalValidator)
9 .setNext(geocodingValidator)
10 .setNext(deliverabilityValidator);
11
12// Use the chain
13const 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

  1. Multiple handlers - More than one object may handle a request
  2. Dynamic handlers - Set of handlers determined at runtime
  3. Sequential processing - Handlers should be tried in order
  4. Decoupling - Sender shouldn't know which handler will process

Logistics Use Cases

ChainHandlers
Address ValidationFormat → Postal → Geocoding → Deliverability
Order ApprovalClerk → Manager → Director
Discount CalculationLoyalty → Volume → Promotional → Seasonal
Package RoutingLocal → 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.