Extending Behavior Without Deep Inheritance
In this lesson, you will learn how to add new responsibilities to objects dynamically by "wrapping" them in decorator classes instead of creating complex inheritance trees.
The Problem with Deep Inheritance
Imagine you have a BasicCoffee class. If you want a coffee with Milk and a coffee with Sugar, you might be tempted to create CoffeeWithMilk and CoffeeWithSugar classes. But what if you want both? You end up with a messy hierarchy like CoffeeWithMilkAndSugar. This "deep inheritance" is rigid and difficult to maintain.
The Decorator Pattern
Instead of inheritance, we use composition. We create a common interface (or abstract base class) that both our core object and our decorators implement. The decorator holds a reference to the object it is "decorating" and calls its methods, while adding its own unique behavior before or after that call.
Here is how you wrap functionality:
public interface ICoffee {
string GetDescription();
}
public class BasicCoffee : ICoffee {
public string GetDescription() => "Basic Coffee";
}
// The Decorator
public class MilkDecorator : ICoffee {
private readonly ICoffee _coffee;
public MilkDecorator(ICoffee coffee) => _coffee = coffee;
public string GetDescription() => _coffee.GetDescription() + ", plus Milk";
}
Why This Matters
By using this approach, you follow the Liskov Substitution Principle because the decorator acts like the object it wraps. You can chain as many decorators as you like at runtime:
ICoffee myOrder = new BasicCoffee();
myOrder = new MilkDecorator(myOrder);
myOrder = new SugarDecorator(myOrder); // Imagine a SugarDecorator exists
This keeps your code flexible. You aren't forcing your classes to inherit from each other to gain features; you are simply wrapping them in layers, much like putting on a coat over a shirt. You can add or remove behavior without modifying the original class source code, which helps prevent the Fragile Base Class Syndrome we discussed earlier.
Key Takeaway: The Decorator Pattern allows you to extend object behavior dynamically by wrapping instances in polymorphic objects rather than creating static, deep inheritance hierarchies.