Step-by-Step Guide to Implementing the Decorator Pattern
The Decorator Pattern is a structural design pattern that allows you to dynamically add behavior to an object without affecting the behavior of other objects from the same class. It provides a flexible alternative to subclassing for extending functionality.
1. Understand the Concept
The Decorator Pattern involves the following key components:
- Component Interface: An abstract class or interface that defines the operations.
- Concrete Component: A class that implements the Component interface.
- Decorator: An abstract class that implements the Component interface and has a reference to a Component object.
- Concrete Decorators: Classes that extend the Decorator class and add functionalities to the Component.
2. Define the Example Scenario
Let's consider a real-world example: A coffee shop where you can order different types of coffee and add various condiments (like milk, sugar, and whipped cream) dynamically.
3. Implementing the Example
Step 1: Define the Component Interface
First, create an interface or abstract class for the coffee:
Step 2: Create Concrete Components
Next, create concrete implementations of the Coffee class:
public class Espresso : Coffee
{
public override string GetDescription()
{
return "Espresso";
}
public override double Cost()
{
return 1.99;
}
}
public class HouseBlend : Coffee
{
public override string GetDescription()
{
return "House Blend Coffee";
}
public override double Cost()
{
return 0.89;
}
}
Step 3: Create the Decorator Abstract Class
Create an abstract decorator class that implements the Coffee interface and holds a reference to a Coffee object:
public abstract class CoffeeDecorator : Coffee
{
protected Coffee _coffee;
public CoffeeDecorator(Coffee coffee)
{
_coffee = coffee;
}
public override string GetDescription()
{
return _coffee.GetDescription();
}
public override double Cost()
{
return _coffee.Cost();
}
}
Step 4: Create Concrete Decorators
Now, create concrete decorators that extend the CoffeeDecorator class and add functionalities:
public class Milk : CoffeeDecorator
{
public Milk(Coffee coffee) : base(coffee)
{
}
public override string GetDescription()
{
return _coffee.GetDescription() + ", Milk";
}
public override double Cost()
{
return _coffee.Cost() + 0.50;
}
}
public class Sugar : CoffeeDecorator
{
public Sugar(Coffee coffee) : base(coffee)
{
}
public override string GetDescription()
{
return _coffee.GetDescription() + ", Sugar";
}
public override double Cost()
{
return _coffee.Cost() + 0.20;
}
}
public class WhippedCream : CoffeeDecorator
{
public WhippedCream(Coffee coffee) : base(coffee)
{
}
public override string GetDescription()
{
return _coffee.GetDescription() + ", Whipped Cream";
}
public override double Cost()
{
return _coffee.Cost() + 0.70;
}
}
Step 5: Use the Decorators
Finally, use the decorators to dynamically add functionalities to the coffee objects:
class Program
{
static void Main(string[] args)
{
Coffee myCoffee = new Espresso();
Console.WriteLine($"{myCoffee.GetDescription()} ${myCoffee.Cost()}");
myCoffee = new Milk(myCoffee);
Console.WriteLine($"{myCoffee.GetDescription()} ${myCoffee.Cost()}");
myCoffee = new Sugar(myCoffee);
Console.WriteLine($"{myCoffee.GetDescription()} ${myCoffee.Cost()}");
myCoffee = new WhippedCream(myCoffee);
Console.WriteLine($"{myCoffee.GetDescription()} ${myCoffee.Cost()}");
// Output:
// Espresso $1.99
// Espresso, Milk $2.49
// Espresso, Milk, Sugar $2.69
// Espresso, Milk, Sugar, Whipped Cream $3.39
}
}
Summary
- Step 1: Define a Component interface or abstract class.
- Step 2: Create Concrete Components that implement the Component interface.
- Step 3: Create an abstract Decorator class that also implements the Component interface and holds a reference to a Component object.
- Step 4: Create Concrete Decorators that extend the Decorator class and add functionalities.
- Step 5: Use the decorators to dynamically add behaviors to the component.
By following these steps, you can implement the Decorator Pattern to add functionalities to objects dynamically, keeping your design flexible and adherent to the Open/Closed Principle. This approach helps in maintaining a clean and extendable codebase.
Top comments (0)