Menu
Coddy logo textTech

Decorator Pattern

Part of the Object Oriented Programming section of Coddy's C# journey — lesson 58 of 70.

The Decorator pattern is a structural pattern that lets you attach new behaviors to objects by wrapping them in special objects called decorators. Unlike inheritance, which adds behavior at compile time, decorators add functionality dynamically at runtime without modifying the original class.

The pattern works by having decorators implement the same interface as the object they wrap. Each decorator holds a reference to the wrapped object and delegates calls to it, adding its own behavior before or after:

public interface ICoffee
{
    string GetDescription();
    decimal GetCost();
}

public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Coffee";
    public decimal GetCost() => 2.00m;
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    
    public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
    
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual decimal GetCost() => _coffee.GetCost();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", Milk";
    public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}

The power of decorators lies in their ability to stack. You can wrap a decorated object with another decorator:

ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee);  // Double milk!

Console.WriteLine(coffee.GetDescription());  // Coffee, Milk, Milk
Console.WriteLine(coffee.GetCost());         // 3.00

The Decorator pattern is ideal when you need to add responsibilities to objects without creating an explosion of subclasses. Each decorator focuses on a single concern, and you combine them as needed to build the exact behavior you want.

challenge icon

Challenge

Easy

Let's build a pizza ordering system using the Decorator pattern. You'll create a base pizza and then dynamically add toppings to it, with each topping wrapping the previous layer and adding to both the description and the price.

You'll organize your code across three files:

  • Pizza.cs: Define an IPizza interface in the PizzaShop namespace with two methods: GetDescription() returning a string and GetPrice() returning a decimal. Then create a PlainPizza class that implements this interface - it should return "Pizza" as its description and 8.00m as its base price.
  • ToppingDecorators.cs: Create an abstract ToppingDecorator class that implements IPizza and holds a reference to the wrapped pizza (passed through the constructor). Its virtual methods should delegate to the wrapped pizza. Then create three concrete decorators:
    • CheeseTopping - appends ", Cheese" to the description and adds 1.50m to the price
    • PepperoniTopping - appends ", Pepperoni" to the description and adds 2.00m to the price
    • MushroomTopping - appends ", Mushrooms" to the description and adds 1.25m to the price
  • Program.cs: Bring everything together by starting with a plain pizza and wrapping it with toppings based on input. Each topping decorator wraps the previous pizza, building up the final order layer by layer.

You will receive one input:

  • A comma-separated list of toppings to add (e.g., cheese,pepperoni,cheese)

Start with a PlainPizza, then wrap it with the appropriate decorator for each topping in the order they appear. Valid toppings are cheese, pepperoni, and mushrooms. After applying all toppings, print the description on one line and the total price on the next line.

For example, if the input is cheese,pepperoni,mushrooms, the output should be:

Pizza, Cheese, Pepperoni, Mushrooms
12.75

Notice how you can add the same topping multiple times! If the input is cheese,cheese, you'd get:

Pizza, Cheese, Cheese
11

This demonstrates the decorator pattern's flexibility - each decorator wraps whatever pizza it receives, whether that's a plain pizza or an already-decorated one, allowing you to stack behaviors dynamically!

Cheat sheet

The Decorator pattern is a structural pattern that attaches new behaviors to objects by wrapping them in special decorator objects. It adds functionality dynamically at runtime without modifying the original class.

Decorators implement the same interface as the object they wrap, hold a reference to the wrapped object, and delegate calls to it while adding their own behavior:

public interface ICoffee
{
    string GetDescription();
    decimal GetCost();
}

public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Coffee";
    public decimal GetCost() => 2.00m;
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    
    public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
    
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual decimal GetCost() => _coffee.GetCost();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", Milk";
    public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}

Decorators can be stacked by wrapping a decorated object with another decorator:

ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee);  // Double milk!

Console.WriteLine(coffee.GetDescription());  // Coffee, Milk, Milk
Console.WriteLine(coffee.GetCost());         // 3.00

Use the Decorator pattern when you need to add responsibilities to objects without creating many subclasses. Each decorator focuses on a single concern and can be combined as needed.

Try it yourself

using System;
using PizzaShop;

class Program
{
    public static void Main(string[] args)
    {
        // Read the comma-separated list of toppings
        string input = Console.ReadLine();
        string[] toppings = input.Split(',');
        
        // TODO: Start with a PlainPizza
        
        // TODO: Loop through each topping and wrap the pizza
        // with the appropriate decorator based on the topping name:
        // - "cheese" -> CheeseTopping
        // - "pepperoni" -> PepperoniTopping
        // - "mushrooms" -> MushroomTopping
        
        // TODO: Print the final description and price
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming