Design Patterns: Decorator Pattern

By | 2016-01-03

The Decorator Pattern enables us to dynamically add functionality to a class without modifying the class. We extend or decorate a class with other classes that essentially acts as a wrapper. A class can have as many decorators as necessary that wrap additional functionality onto a class. An argument could be made, why not include the functionality in the class and turn it off or on with a Boolean property….but that is not a scalable solution. To show the capability of decorators we will look at an example of a pizzeria. A pizzeria will carry several different types of pizzas and a whole selection of toppings. If all the logic was included in a single class it would involve a rewrite of the class anytime changes are to be made. If we follow Solid Principles, we know that the the Open/Closed Principle says that classes should be open to extension but closed to modification. So if we want to be able to construct all the different types of types of pizzas with all the different combinations of toppings dynamically without modifying any of the base classes anytime toppings change, then the decorator pattern is a perfect choice to extend our classes. What would this look like?

First off we would define an abstract base class for our pizza that contains a field to store a description of the pizza, a virtual method to return our desciption, and an abstract method to return the cost:

internal abstract class Pizza
{
    public string description = "Unknown Pizza type";

    public virtual string getDescription()
    {
        return description;
    }

    public abstract double cost();
}

Next we make concrete classes of our main pizza types:

internal class ThinCrust : Pizza
{
    public ThinCrust()
    {
        description = "Thin Crust";
    }

    public override double cost()
    {
        return 5.00;
    }
}

internal class PanCrust : Pizza
{
    public PanCrust()
    {
        description = "Pan Crust";
    }
    public override double cost()
    {
        return 7.00;
    }
}

internal class Sicilian : Pizza
{
    public Sicilian()
    {
        description = "Sicilian";
    }

    public override double cost()
    {
        return 10.00;
    }
}

Next we create an abstract base class for our decorators (toppings in this case) that inherits from the Pizza base class to maintain the same base type for our decorators to extend the concrete Pizza classes. This new base class for our decorators will provide an abstract override of the getDescription() method to extend this method:

internal abstract class ToppingsDecorator : Pizza
{
    public abstract override string getDescription();
}

Now all of our decorators(toppings) will inherit this new base class. These decorators will accept an instance of the Pizza class and store it in a field to use in the overrides of the cost() and getDescription() methods:

internal class Ham : ToppingsDecorator
{
    Pizza _pizza;

    public Ham(Pizza pizza)
    {
        _pizza = pizza;
    }

    public override double cost()
    {
        return _pizza.cost() + .20;
    }

    public override string getDescription()
    {
        return _pizza.getDescription() + ", Ham";
    }
}

internal class Bacon : ToppingsDecorator
{
    Pizza _pizza;

    public Bacon(Pizza pizza)
    {
        _pizza = pizza;
    }
    public override double cost()
    {
        return _pizza.cost() + .20;
    }

    public override string getDescription()
    {
        return _pizza.getDescription() + ", Bacon";
    }
}

internal class Pepperoni : ToppingsDecorator
{
    Pizza _pizza;
    public Pepperoni(Pizza pizza)
    {
        _pizza = pizza;
    }

    public override double cost()
    {
        return _pizza.cost() + .20;
    }

    public override string getDescription()
    {
        return _pizza.getDescription() + ", Pepperoni";
    }
}

Each one of these decorators takes a Pizza instance and adds its cost onto the pizza and updates the description.
Now the decorators get called like this:

static void Main(string[] args)
{

    Pizza pizza = new ThinCrust();
    pizza = new Pepperoni(pizza);
    Console.WriteLine("{0} ${1}", pizza.getDescription(), pizza.cost());

    Pizza pizza2 = new PanCrust();
    pizza2 = new Pepperoni(pizza2);
    pizza2 = new Ham(pizza2);
    pizza2 = new Bacon(pizza2);
    Console.WriteLine("{0} ${1}", pizza2.getDescription(), pizza2.cost());

    Pizza pizza3 = new Sicilian();
    pizza3 = new Pepperoni(pizza3);
    pizza3 = new Ham(pizza3);
    pizza3 = new Bacon(pizza3);
    Console.WriteLine("{0} ${1}", pizza3.getDescription(), pizza3.cost());

    Console.ReadLine();

}

Output:
Thin Curst, Pepperoni, $5.2
Pan Crust, Pepperoni, Ham, Bacon $7.6
Sicilian, Pepperoni, Ham, Bacon $10.6

A pizza gets created and then any toppings are added, description updated and cost calculated. This keeps everything separated and dynamically extendable to create all of the options required while keeping everything easily maintainable for the future. New toppings can be added or taken away without ever having to modify any of the pizza type classes. Decorators = easy extensibility.