20 minlesson

Introduction to Decorator Pattern

Introduction to Decorator Pattern

Decorator attaches additional responsibilities to an object dynamically. It provides a flexible alternative to subclassing for extending functionality.

The Problem

Adding shipping options through inheritance:

typescript
1// Inheritance explosion
2class StandardShipment { }
3class InsuredShipment extends StandardShipment { }
4class SignatureRequiredShipment extends StandardShipment { }
5class FragileShipment extends StandardShipment { }
6
7// Combinations multiply classes
8class InsuredSignatureShipment extends StandardShipment { }
9class InsuredFragileShipment extends StandardShipment { }
10class SignatureFragileShipment extends StandardShipment { }
11class InsuredSignatureFragileShipment extends StandardShipment { }
12// 3 options = 8 combinations!

The Solution: Decorator

Wrap objects to add behavior:

typescript
1// Component interface
2interface Shipment {
3 getDescription(): string;
4 getCost(): number;
5}
6
7// Concrete component
8class StandardShipment implements Shipment {
9 constructor(private baseRate: number) {}
10
11 getDescription(): string {
12 return 'Standard Shipment';
13 }
14
15 getCost(): number {
16 return this.baseRate;
17 }
18}
19
20// Base decorator
21abstract class ShipmentDecorator implements Shipment {
22 constructor(protected shipment: Shipment) {}
23
24 getDescription(): string {
25 return this.shipment.getDescription();
26 }
27
28 getCost(): number {
29 return this.shipment.getCost();
30 }
31}
32
33// Concrete decorators
34class InsuranceDecorator extends ShipmentDecorator {
35 constructor(shipment: Shipment, private insuredValue: number) {
36 super(shipment);
37 }
38
39 getDescription(): string {
40 return `${this.shipment.getDescription()} + Insurance ($${this.insuredValue})`;
41 }
42
43 getCost(): number {
44 return this.shipment.getCost() + this.insuredValue * 0.02;
45 }
46}
47
48class SignatureDecorator extends ShipmentDecorator {
49 getDescription(): string {
50 return `${this.shipment.getDescription()} + Signature Required`;
51 }
52
53 getCost(): number {
54 return this.shipment.getCost() + 3.50;
55 }
56}
57
58class FragileDecorator extends ShipmentDecorator {
59 getDescription(): string {
60 return `${this.shipment.getDescription()} + Fragile Handling`;
61 }
62
63 getCost(): number {
64 return this.shipment.getCost() + 5.00;
65 }
66}

Usage

typescript
1// Start with base shipment
2let shipment: Shipment = new StandardShipment(10.00);
3console.log(shipment.getCost()); // 10.00
4
5// Add insurance
6shipment = new InsuranceDecorator(shipment, 500);
7console.log(shipment.getCost()); // 20.00 (10 + 500*0.02)
8
9// Add signature requirement
10shipment = new SignatureDecorator(shipment);
11console.log(shipment.getCost()); // 23.50
12
13// Add fragile handling
14shipment = new FragileDecorator(shipment);
15console.log(shipment.getCost()); // 28.50
16
17console.log(shipment.getDescription());
18// "Standard Shipment + Insurance ($500) + Signature Required + Fragile Handling"

Pattern Structure

1┌─────────────────────┐
2│ Component │
3│ (interface) │
4│─────────────────────│
5│ + operation() │
6└──────────┬──────────┘
7
8 ┌─────┴─────┐
9 │ │
10┌────▼────┐ ┌────▼──────────────┐
11│Concrete │ │ Decorator │
12│Component│ │───────────────────│
13│ │ │ - component │◄───┐
14└─────────┘ │ + operation() │ │
15 └────────┬──────────┘ │
16 │ │
17 ┌─────────┴─────────┐ │
18 │ │ │
19 ┌──────▼──────┐ ┌───────▼───┐ │
20 │ DecoratorA │ │DecoratorB │ │
21 │─────────────│ │───────────│ │
22 │+ operation()│ │+operation()│ │
23 └─────────────┘ └───────────┘ │
24 │ │
25 └─────────────────────────┘
26 (decorators can wrap other decorators)

Key Participants

  1. Component - Interface for objects that can have responsibilities added
  2. ConcreteComponent - The object being decorated
  3. Decorator - Maintains reference to component, delegates to it
  4. ConcreteDecorator - Adds responsibilities before/after delegating

When to Use Decorator

  1. Dynamic extension - Add responsibilities at runtime
  2. Optional features - Mix-and-match combinations
  3. Avoid inheritance explosion - Too many subclass combinations
  4. Transparent wrapping - Decorators look like the original object

Logistics Applications

DecoratorAdds
InsuranceValue coverage, premium cost
SignatureDelivery confirmation
FragileSpecial handling
ExpressPriority processing
TrackingReal-time updates
Gift WrapPresentation packaging

Summary

Decorator enables flexible addition of behavior by wrapping objects. Stack decorators to combine features without creating a class for every combination.