Decorator Pattern - Adding Behavior to Objects Dynamically

Learning Objectives: By the end of this lesson, you will be able to:

  • Understand the Decorator pattern and when to use it for extending object behavior
  • Implement flexible decoration chains that can be combined in any order
  • Apply Decorator pattern to real-world scenarios like middleware and data processing
  • Recognize the benefits of composition over inheritance for behavior extension
  • Design systems that follow the Open/Closed Principle using decorators

1. Welcome and Introduction (5 minutes)

Icebreaker Activity

Think about customizing a coffee order - you start with a base coffee and add extras like milk, sugar, whipped cream, flavoring syrups. Each addition enhances the coffee without changing its fundamental nature. How is this similar to adding features to software objects?

Lesson Overview

  • Duration: 120 minutes
  • Prerequisites: Object-oriented programming, interfaces, composition concepts
  • Materials Needed: Development environment with Java, Python, or JavaScript support

2. Learning Objectives and Session Outline (5 minutes)

What We’ll Cover Today:

  1. Problem Definition - Limitations of inheritance for adding behavior combinations
  2. Decorator Pattern Structure - Component, Decorator, and Concrete Decorators
  3. Implementation Approaches - Traditional decorators, functional decorators, and middleware patterns
  4. Real-world Applications - I/O streams, web middleware, data transformation pipelines
  5. Design Considerations - Order dependency, performance implications, and testing strategies
  6. Modern Usage - Annotations, Python decorators, and framework implementations

Success Criteria:

By the end of this session, you should be able to create flexible decorator systems that extend object behavior without modifying existing code.

3. Foundation Knowledge (15-20 minutes)

Core Concepts

What is the Decorator Pattern?

The Decorator pattern attaches additional responsibilities to an object dynamically. It provides a flexible alternative to subclassing for extending functionality, allowing behavior to be added to objects without altering their structure.

Real-World Analogy: Consider decorating a Christmas tree. You start with a basic tree and add lights, ornaments, tinsel, and a star. Each decoration adds to the tree’s appearance without changing the tree itself. You can combine decorations in any order and quantity.

Why This Matters:

  • Flexibility: Add or remove behaviors at runtime
  • Composition: Combine multiple behaviors in any order
  • Single Responsibility: Each decorator has one specific enhancement
  • Open/Closed Principle: Add new decorators without modifying existing code

Problems Decorator Pattern Solves

Problem 1: Inheritance Explosion

// Bad: Creating classes for every combination
class Coffee { }
class CoffeeWithMilk extends Coffee { }
class CoffeeWithSugar extends Coffee { }
class CoffeeWithMilkAndSugar extends Coffee { }
class CoffeeWithMilkAndSugarAndWhip extends Coffee { }
// This grows exponentially with each new addition!

Problem 2: Static Behavior Assignment

// Bad: Behavior determined at compile time
class FileProcessor {
    private boolean shouldCompress;
    private boolean shouldEncrypt;
    private boolean shouldLog;
    
    // Constructor must specify all options upfront
    public FileProcessor(boolean compress, boolean encrypt, boolean log) {
        this.shouldCompress = compress;
        this.shouldEncrypt = encrypt;
        this.shouldLog = log;
    }
    
    public void processFile(File file) {
        // Complex conditional logic for combinations
        if (shouldLog) { /* log */ }
        if (shouldCompress) { /* compress */ }
        if (shouldEncrypt) { /* encrypt */ }
        // Hard to add new behaviors or change combinations
    }
}

Key Terminology

  • Component: Interface that defines the basic behavior
  • Concrete Component: Basic implementation of the component
  • Decorator: Abstract class that implements the component interface and has a component reference
  • Concrete Decorator: Specific enhancement that adds behavior to the component
  • Decoration Chain: Series of decorators applied to a component

4. Detailed Explanation and Examples (25-30 minutes)

Understanding Decorator Pattern Structure

Classic Decorator Pattern Implementation

// Component interface
interface Coffee {
    String getDescription();
    double getCost();
}

// Concrete component
class SimpleCoffee implements Coffee {
    @Override
    public String getDescription() {
        return "Simple coffee";
    }
    
    @Override
    public double getCost() {
        return 2.00;
    }
}

// Abstract decorator
abstract class CoffeeDecorator implements Coffee {
    protected Coffee coffee;
    
    public CoffeeDecorator(Coffee coffee) {
        this.coffee = coffee;
    }
    
    @Override
    public String getDescription() {
        return coffee.getDescription();
    }
    
    @Override
    public double getCost() {
        return coffee.getCost();
    }
}

// Concrete decorators
class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }
    
    @Override
    public String getDescription() {
        return coffee.getDescription() + ", milk";
    }
    
    @Override
    public double getCost() {
        return coffee.getCost() + 0.50;
    }
}

class SugarDecorator extends CoffeeDecorator {
    public SugarDecorator(Coffee coffee) {
        super(coffee);
    }
    
    @Override
    public String getDescription() {
        return coffee.getDescription() + ", sugar";
    }
    
    @Override
    public double getCost() {
        return coffee.getCost() + 0.25;
    }
}

class WhipDecorator extends CoffeeDecorator {
    public WhipDecorator(Coffee coffee) {
        super(coffee);
    }
    
    @Override
    public String getDescription() {
        return coffee.getDescription() + ", whipped cream";
    }
    
    @Override
    public double getCost() {
        return coffee.getCost() + 0.75;
    }
}

class FlavorDecorator extends CoffeeDecorator {
    private String flavor;
    
    public FlavorDecorator(Coffee coffee, String flavor) {
        super(coffee);
        this.flavor = flavor;
    }
    
    @Override
    public String getDescription() {
        return coffee.getDescription() + ", " + flavor + " flavor";
    }
    
    @Override
    public double getCost() {
        return coffee.getCost() + 0.60;
    }
}

// Usage demonstration
public class CoffeeShop {
    public static void main(String[] args) {
        // Start with simple coffee
        Coffee coffee = new SimpleCoffee();
        System.out.println(coffee.getDescription() + " - $" + coffee.getCost());
        
        // Add milk
        coffee = new MilkDecorator(coffee);
        System.out.println(coffee.getDescription() + " - $" + coffee.getCost());
        
        // Add sugar
        coffee = new SugarDecorator(coffee);
        System.out.println(coffee.getDescription() + " - $" + coffee.getCost());
        
        // Add whipped cream
        coffee = new WhipDecorator(coffee);
        System.out.println(coffee.getDescription() + " - $" + coffee.getCost());
        
        // Add vanilla flavor
        coffee = new FlavorDecorator(coffee, "vanilla");
        System.out.println(coffee.getDescription() + " - $" + coffee.getCost());
        
        System.out.println("\n--- Creating Different Combinations ---");
        
        // Different combination
        Coffee specialCoffee = new FlavorDecorator(
            new WhipDecorator(
                new MilkDecorator(
                    new SimpleCoffee()
                )
            ), "caramel"
        );
        System.out.println(specialCoffee.getDescription() + " - $" + specialCoffee.getCost());
    }
}

Data Processing Pipeline with Decorators

from abc import ABC, abstractmethod
from typing import Any, Dict, List
import json
import base64
import gzip

# Component interface
class DataProcessor(ABC):
    @abstractmethod
    def process(self, data: Any) -> Any:
        pass
    
    @abstractmethod
    def get_description(self) -> str:
        pass

# Concrete component
class BasicDataProcessor(DataProcessor):
    def process(self, data: Any) -> Any:
        return data
    
    def get_description(self) -> str:
        return "Basic processor"

# Abstract decorator
class DataProcessorDecorator(DataProcessor):
    def __init__(self, processor: DataProcessor):
        self._processor = processor
    
    def process(self, data: Any) -> Any:
        return self._processor.process(data)
    
    def get_description(self) -> str:
        return self._processor.get_description()

# Concrete decorators
class ValidationDecorator(DataProcessorDecorator):
    def __init__(self, processor: DataProcessor, required_fields: List[str]):
        super().__init__(processor)
        self.required_fields = required_fields
    
    def process(self, data: Any) -> Any:
        # Validate data before processing
        if isinstance(data, dict):
            missing_fields = [field for field in self.required_fields if field not in data]
            if missing_fields:
                raise ValueError(f"Missing required fields: {missing_fields}")
        
        return super().process(data)
    
    def get_description(self) -> str:
        return f"{super().get_description()} -> Validation({', '.join(self.required_fields)})"

class SerializationDecorator(DataProcessorDecorator):
    def __init__(self, processor: DataProcessor, format_type: str = "json"):
        super().__init__(processor)
        self.format_type = format_type
    
    def process(self, data: Any) -> str:
        processed_data = super().process(data)
        
        if self.format_type == "json":
            return json.dumps(processed_data, default=str)
        elif self.format_type == "base64":
            json_str = json.dumps(processed_data, default=str)
            return base64.b64encode(json_str.encode()).decode()
        else:
            return str(processed_data)
    
    def get_description(self) -> str:
        return f"{super().get_description()} -> Serialization({self.format_type})"

class CompressionDecorator(DataProcessorDecorator):
    def process(self, data: Any) -> bytes:
        processed_data = super().process(data)
        
        if isinstance(processed_data, str):
            data_bytes = processed_data.encode()
        elif isinstance(processed_data, bytes):
            data_bytes = processed_data
        else:
            data_bytes = str(processed_data).encode()
        
        return gzip.compress(data_bytes)
    
    def get_description(self) -> str:
        return f"{super().get_description()} -> Compression(gzip)"

class EncryptionDecorator(DataProcessorDecorator):
    def __init__(self, processor: DataProcessor, key: str = "default_key"):
        super().__init__(processor)
        self.key = key
    
    def process(self, data: Any) -> str:
        processed_data = super().process(data)
        
        # Simple XOR encryption for demonstration
        if isinstance(processed_data, bytes):
            data_str = base64.b64encode(processed_data).decode()
        else:
            data_str = str(processed_data)
        
        key_bytes = self.key.encode()
        encrypted = bytearray()
        
        for i, byte in enumerate(data_str.encode()):
            encrypted.append(byte ^ key_bytes[i % len(key_bytes)])
        
        return base64.b64encode(encrypted).decode()
    
    def get_description(self) -> str:
        return f"{super().get_description()} -> Encryption(XOR)"

class LoggingDecorator(DataProcessorDecorator):
    def __init__(self, processor: DataProcessor, log_level: str = "INFO"):
        super().__init__(processor)
        self.log_level = log_level
        self.operation_count = 0
    
    def process(self, data: Any) -> Any:
        self.operation_count += 1
        
        print(f"[{self.log_level}] Processing operation #{self.operation_count}")
        print(f"[{self.log_level}] Input type: {type(data).__name__}")
        
        try:
            result = super().process(data)
            print(f"[{self.log_level}] Processing completed successfully")
            print(f"[{self.log_level}] Output type: {type(result).__name__}")
            return result
        except Exception as e:
            print(f"[ERROR] Processing failed: {e}")
            raise

# Usage demonstration
def demonstrate_data_processing():
    print("=== Data Processing Pipeline Demo ===\n")
    
    # Sample data
    sample_data = {
        "user_id": "12345",
        "name": "John Doe",
        "email": "john@example.com",
        "age": 30,
        "preferences": {
            "theme": "dark",
            "notifications": True
        }
    }
    
    print("Original data:")
    print(json.dumps(sample_data, indent=2))
    print()
    
    # Create different processing pipelines
    
    # Pipeline 1: Basic processing with validation and serialization
    print("=== Pipeline 1: Validation + Serialization ===")
    processor1 = SerializationDecorator(
        ValidationDecorator(
            BasicDataProcessor(),
            required_fields=["user_id", "email"]
        ),
        format_type="json"
    )
    
    print(f"Pipeline: {processor1.get_description()}")
    result1 = processor1.process(sample_data)
    print(f"Result: {result1}")
    print()
    
    # Pipeline 2: Full processing with compression and encryption
    print("=== Pipeline 2: Full Processing Chain ===")
    processor2 = EncryptionDecorator(
        CompressionDecorator(
            SerializationDecorator(
                LoggingDecorator(
                    ValidationDecorator(
                        BasicDataProcessor(),
                        required_fields=["user_id", "name", "email"]
                    ),
                    log_level="DEBUG"
                ),
                format_type="json"
            )
        ),
        key="secret_key_123"
    )
    
    print(f"Pipeline: {processor2.get_description()}")
    result2 = processor2.process(sample_data)
    print(f"Encrypted result length: {len(result2)} characters")
    print()
    
    # Pipeline 3: Alternative order
    print("=== Pipeline 3: Different Order ===")
    processor3 = LoggingDecorator(
        EncryptionDecorator(
            SerializationDecorator(
                CompressionDecorator(
                    ValidationDecorator(
                        BasicDataProcessor(),
                        required_fields=["user_id"]
                    )
                ),
                format_type="base64"
            ),
            key="another_key"
        ),
        log_level="INFO"
    )
    
    print(f"Pipeline: {processor3.get_description()}")
    result3 = processor3.process(sample_data)
    print(f"Final result: {result3[:50]}..." if len(result3) > 50 else result3)

demonstrate_data_processing()

5. Live Coding Demonstration (20-25 minutes)

Example 1: Web Middleware System

// Base handler interface
class RequestHandler {
    handle(request, response) {
        throw new Error("handle method must be implemented");
    }
}

// Basic request handler
class BasicHandler extends RequestHandler {
    handle(request, response) {
        response.body = `Handled request to ${request.path}`;
        response.status = 200;
        return response;
    }
}

// Abstract middleware decorator
class MiddlewareDecorator extends RequestHandler {
    constructor(handler) {
        super();
        this.handler = handler;
    }
    
    handle(request, response) {
        return this.handler.handle(request, response);
    }
}

// Concrete middleware decorators
class AuthenticationMiddleware extends MiddlewareDecorator {
    constructor(handler, requiredRole = 'user') {
        super(handler);
        this.requiredRole = requiredRole;
    }
    
    handle(request, response) {
        console.log(`🔐 Authentication middleware: Checking auth for ${request.path}`);
        
        if (!request.headers.authorization) {
            response.status = 401;
            response.body = 'Authentication required';
            return response;
        }
        
        // Simulate token validation
        const token = request.headers.authorization.replace('Bearer ', '');
        const user = this.validateToken(token);
        
        if (!user) {
            response.status = 401;
            response.body = 'Invalid token';
            return response;
        }
        
        if (user.role !== this.requiredRole && this.requiredRole !== 'user') {
            response.status = 403;
            response.body = 'Insufficient permissions';
            return response;
        }
        
        request.user = user;
        console.log(`✅ User authenticated: ${user.username} (${user.role})`);
        
        return super.handle(request, response);
    }
    
    validateToken(token) {
        // Simulate token validation
        const users = {
            'user123': { username: 'john_doe', role: 'user' },
            'admin456': { username: 'admin', role: 'admin' }
        };
        return users[token] || null;
    }
}

class LoggingMiddleware extends MiddlewareDecorator {
    constructor(handler, logLevel = 'INFO') {
        super(handler);
        this.logLevel = logLevel;
        this.requestCount = 0;
    }
    
    handle(request, response) {
        this.requestCount++;
        const startTime = Date.now();
        
        console.log(`📝 [${this.logLevel}] Request #${this.requestCount}: ${request.method} ${request.path}`);
        console.log(`📝 [${this.logLevel}] Headers: ${JSON.stringify(request.headers)}`);
        
        const result = super.handle(request, response);
        
        const duration = Date.now() - startTime;
        console.log(`📝 [${this.logLevel}] Response: ${result.status} (${duration}ms)`);
        
        return result;
    }
}

class RateLimitingMiddleware extends MiddlewareDecorator {
    constructor(handler, maxRequests = 10, windowMs = 60000) {
        super(handler);
        this.maxRequests = maxRequests;
        this.windowMs = windowMs;
        this.requests = new Map();
    }
    
    handle(request, response) {
        const clientIp = request.headers['x-forwarded-for'] || request.ip || 'unknown';
        const now = Date.now();
        
        console.log(`⏱️  Rate limiting check for IP: ${clientIp}`);
        
        if (!this.requests.has(clientIp)) {
            this.requests.set(clientIp, []);
        }
        
        const clientRequests = this.requests.get(clientIp);
        
        // Remove old requests outside the window
        const validRequests = clientRequests.filter(time => now - time < this.windowMs);
        this.requests.set(clientIp, validRequests);
        
        if (validRequests.length >= this.maxRequests) {
            console.log(`🚫 Rate limit exceeded for IP: ${clientIp}`);
            response.status = 429;
            response.body = 'Rate limit exceeded. Please try again later.';
            return response;
        }
        
        validRequests.push(now);
        console.log(`✅ Rate limit OK: ${validRequests.length}/${this.maxRequests} requests`);
        
        return super.handle(request, response);
    }
}

class ValidationMiddleware extends MiddlewareDecorator {
    constructor(handler, rules = {}) {
        super(handler);
        this.rules = rules;
    }
    
    handle(request, response) {
        console.log(`✔️  Validation middleware: Checking request validity`);
        
        const errors = [];
        
        // Validate required fields
        if (this.rules.requiredFields) {
            for (const field of this.rules.requiredFields) {
                if (!request.body || !request.body[field]) {
                    errors.push(`Missing required field: ${field}`);
                }
            }
        }
        
        // Validate field types
        if (this.rules.fieldTypes && request.body) {
            for (const [field, expectedType] of Object.entries(this.rules.fieldTypes)) {
                if (request.body[field] && typeof request.body[field] !== expectedType) {
                    errors.push(`Invalid type for ${field}: expected ${expectedType}`);
                }
            }
        }
        
        if (errors.length > 0) {
            console.log(`❌ Validation failed: ${errors.join(', ')}`);
            response.status = 400;
            response.body = { errors };
            return response;
        }
        
        console.log(`✅ Validation passed`);
        return super.handle(request, response);
    }
}

class CorsMiddleware extends MiddlewareDecorator {
    constructor(handler, options = {}) {
        super(handler);
        this.allowedOrigins = options.allowedOrigins || ['*'];
        this.allowedMethods = options.allowedMethods || ['GET', 'POST', 'PUT', 'DELETE'];
        this.allowedHeaders = options.allowedHeaders || ['Content-Type', 'Authorization'];
    }
    
    handle(request, response) {
        console.log(`🌐 CORS middleware: Setting headers`);
        
        const origin = request.headers.origin;
        
        if (this.allowedOrigins.includes('*') || this.allowedOrigins.includes(origin)) {
            response.headers = response.headers || {};
            response.headers['Access-Control-Allow-Origin'] = origin || '*';
            response.headers['Access-Control-Allow-Methods'] = this.allowedMethods.join(', ');
            response.headers['Access-Control-Allow-Headers'] = this.allowedHeaders.join(', ');
            
            console.log(`✅ CORS headers set for origin: ${origin || 'any'}`);
        }
        
        // Handle preflight requests
        if (request.method === 'OPTIONS') {
            response.status = 200;
            response.body = '';
            return response;
        }
        
        return super.handle(request, response);
    }
}

// Usage demonstration
function demonstrateMiddleware() {
    console.log('=== Web Middleware System Demo ===\n');
    
    // Create different handler pipelines
    
    // Pipeline 1: Basic API with auth and logging
    console.log('--- Pipeline 1: Basic API ---');
    const basicApi = new LoggingMiddleware(
        new AuthenticationMiddleware(
            new BasicHandler(),
            'user'
        ),
        'INFO'
    );
    
    const request1 = {
        method: 'GET',
        path: '/api/users',
        headers: {
            'authorization': 'Bearer user123'
        },
        ip: '192.168.1.100'
    };
    
    const response1 = { headers: {} };
    const result1 = basicApi.handle(request1, response1);
    console.log(`Final response: ${result1.status} - ${result1.body}`);
    console.log();
    
    // Pipeline 2: Full middleware stack
    console.log('--- Pipeline 2: Full Middleware Stack ---');
    const fullApi = new LoggingMiddleware(
        new CorsMiddleware(
            new RateLimitingMiddleware(
                new AuthenticationMiddleware(
                    new ValidationMiddleware(
                        new BasicHandler(),
                        {
                            requiredFields: ['name'],
                            fieldTypes: { 'name': 'string', 'age': 'number' }
                        }
                    ),
                    'admin'
                ),
                5, // max 5 requests
                10000 // per 10 seconds
            ),
            {
                allowedOrigins: ['https://myapp.com'],
                allowedMethods: ['GET', 'POST']
            }
        ),
        'DEBUG'
    );
    
    const request2 = {
        method: 'POST',
        path: '/api/admin/users',
        headers: {
            'authorization': 'Bearer admin456',
            'origin': 'https://myapp.com',
            'content-type': 'application/json'
        },
        body: {
            name: 'Jane Doe',
            age: 25
        },
        ip: '192.168.1.101'
    };
    
    const response2 = { headers: {} };
    const result2 = fullApi.handle(request2, response2);
    console.log(`Final response: ${result2.status} - ${result2.body}`);
    console.log(`Response headers:`, result2.headers);
    console.log();
    
    // Pipeline 3: Rate limiting demonstration
    console.log('--- Pipeline 3: Rate Limiting Test ---');
    const rateLimitedApi = new RateLimitingMiddleware(
        new BasicHandler(),
        2, // max 2 requests
        5000 // per 5 seconds
    );
    
    const testRequest = {
        method: 'GET',
        path: '/api/test',
        headers: {},
        ip: '192.168.1.102'
    };
    
    // Make multiple requests to trigger rate limiting
    for (let i = 1; i <= 4; i++) {
        console.log(`Request ${i}:`);
        const response = { headers: {} };
        const result = rateLimitedApi.handle({ ...testRequest }, response);
        console.log(`  Response: ${result.status} - ${result.body}`);
    }
}

demonstrateMiddleware();

Debugging Demonstration

Show common errors and how to fix them:

// Common mistake: Breaking the decorator chain
public class BrokenDecorator extends CoffeeDecorator {
    public BrokenDecorator(Coffee coffee) {
        super(coffee);
    }
    
    @Override
    public double getCost() {
        // Bug: Not calling super.getCost()
        return 1.50; // This breaks the chain!
    }
}

// Fixed version
public class CorrectDecorator extends CoffeeDecorator {
    public CorrectDecorator(Coffee coffee) {
        super(coffee);
    }
    
    @Override
    public double getCost() {
        return super.getCost() + 1.50; // Correctly chains the call
    }
}

6. Hands-On Practice (30-40 minutes)

Exercise 1: Basic Practice (10 minutes)

Objective: Create a text formatting system using Decorator pattern

Instructions:

  1. Create a TextFormatter interface with format(String text) method
  2. Implement decorators: BoldDecorator, ItalicDecorator, UnderlineDecorator
  3. Allow chaining multiple formatting decorators
  4. Test with different combinations and orders

Starter Code:

interface TextFormatter {
    String format(String text);
}

class PlainTextFormatter implements TextFormatter {
    // TODO: Implement basic formatter
}

abstract class TextDecorator implements TextFormatter {
    // TODO: Implement decorator base class
}

Exercise 2: Intermediate Challenge (15 minutes)

Objective: Build a data transformation pipeline using decorators

Requirements:

  • Create transformers for different operations (uppercase, trim, remove spaces, add prefix/suffix)
  • Support chaining transformations in any order
  • Add validation decorators that check data format
  • Include logging decorator that tracks transformation steps

Exercise 3: Creative Application (15 minutes)

Objective: Design a game character enhancement system

Challenge: Create a system where:

  • Base characters can be enhanced with different abilities (strength boost, magic shield, speed enhancement)
  • Enhancements can stack and combine effects
  • Some enhancements have prerequisites or conflicts
  • Include temporary vs permanent enhancements

7. Common Pitfalls and Troubleshooting (10 minutes)

Frequent Mistakes

  1. Problem: Forgetting to call the wrapped component Solution: Always call super() or delegate to wrapped component

  2. Problem: Order dependency between decorators Solution: Document order requirements and consider builder pattern

  3. Problem: Performance issues with deep decorator chains Solution: Consider caching, lazy evaluation, or alternative patterns

8. Best Practices and Professional Tips (5 minutes)

Industry Standards

  • Keep decorators lightweight - each should have a single responsibility
  • Document order dependencies when decorator sequence matters
  • Consider builder pattern for complex decorator chains
  • Use interfaces consistently to maintain substitutability

Next Steps in Your Learning Journey

  • Study Proxy Pattern for controlling access to objects
  • Learn Composite Pattern for tree structures
  • Explore Chain of Responsibility for sequential processing
  • Investigate Aspect-Oriented Programming for cross-cutting concerns

9. Summary and Review (5 minutes)

Key Takeaways

  1. Decorator pattern provides runtime behavior extension without modifying original objects
  2. Composition over inheritance offers more flexibility for combining behaviors
  3. Each decorator should have single responsibility and maintain the component interface
  4. Order matters in some decorator chains - document dependencies clearly

Quick Review Questions

  1. How does Decorator pattern differ from inheritance for extending behavior?
  2. What are the advantages and disadvantages of using deep decorator chains?
  3. When would you choose Decorator pattern over Strategy pattern?

For Instructors

Teaching Tips

  • Start with simple examples (coffee shop) before moving to complex systems
  • Emphasize the importance of maintaining the component interface
  • Show both the flexibility and potential complexity of decorator chains
  • Compare with real-world examples (Java I/O streams, middleware)

Assessment Ideas

  • Refactoring exercise: Convert inheritance hierarchy to decorator pattern
  • Design challenge: Create decorator system for given requirements
  • Performance analysis: Compare decorator chains vs monolithic implementations

Adaptation Notes

  • Beginners: Focus on basic decorator implementation and simple chaining
  • Advanced: Discuss performance optimization, order dependencies, and architectural patterns
  • Framework-specific: Show how frameworks use decorators (Spring AOP, Python decorators)