Introduction
Far too often, we (fellow programmers) make the decision to use standard inheritance when adding new functionality to classes. However, there are many occasions in which this leads to inflexible code. We then scratch our heads and wonder how it happened.
Let me explain with an example. Suppose we have an application that calculates the price of cookies. We create hierarchies like Cookie
, ChocolateChipCookie
, PeanutButterCookie
, etc. Let's say we have a GetPrice()
overloaded method for each class. Everything works great until the cookie factory manager tells us that now they are manufacturing peanut butter chocolate chip cookies. If we then start creating classes (such as a ChocolateChipPeanutButterCookie
), the code becomes unruly rather quickly (in particular trying to separate class responsibilities). So, this design was not flexible enough to handle this simple request.
However, using the decorator pattern is perfect for this scenario. The decorator pattern allows us to add behavior to objects at run-time without directly relying on inheritance. A decorator conforms to the component it is decorating. An example below will make this clear.
Using the Code
In this example, let us create some cost requirements to make it more concrete. Let us assume the base cost of a cookie with its core ingredients is 30 cents, the cost to add chocolate chips is 20 cents, and 40 cents to include peanut butter (my favorite).
Please look at the code below.
Both the ChocolateChipCookie
and PeanutButterCookie
classes allow for an instance of an ICookie
to be passed in the constructor. ChocolateChipCookie
decorates the Cookie
and adds the price of the chocolate chips to the total. The PeanutButter
cookie does the same.
So, to create a cookie that has both chocolate chips and peanut butter, just create a new PeanutButterCookie(chocolateChipCookie);
This allows for your classes to contain any combinations without a rigid hierarchy.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace ConsoleApplication1
{
class Program
{
private interface ICookie
{
float GetPrice();
}
private class Cookie : ICookie
{
public float GetPrice()
{
return (float) 0.30;
}
}
private class ChocolateChipCookie : ICookie
{
private ICookie _cookie;
private float _priceofChocolateChip = (float) 0.20;
public ChocolateChipCookie(ICookie cookie)
{
_cookie = cookie;
}
public float GetPrice()
{
return _cookie.GetPrice() + _priceofChocolateChip;
}
}
private class PeanutButterCookie : ICookie
{
private ICookie _cookie;
private float _priceofPeanutButter = (float) 0.40;
public PeanutButterCookie(ICookie cookie)
{
_cookie = cookie;
}
public float GetPrice()
{
return _cookie.GetPrice() + _priceofPeanutButter;
}
}
static void Main(string[] args)
{
var chocolateChipCookie = new ChocolateChipCookie(new Cookie());
var peanutButterCookie = new PeanutButterCookie(new Cookie());
var peanutButterchocolateChipCookie = new PeanutButterCookie(chocolateChipCookie);
Console.WriteLine("price of chocolate chip cookie: "
+ chocolateChipCookie.GetPrice());
Console.WriteLine("price of peanutbutter cookie: "
+ peanutButterCookie.GetPrice());
Console.WriteLine("price of peanutbutter chocolate chip cookie: "
+ peanutButterchocolateChipCookie.GetPrice());
}
}
}
When executed, you should see the following output:
Price of chocolate chip cookie: 0.5
Price of peanutbutter cookie: 0.7
Price of peanutbutter chocolate chip cookie: 0.9
A helpful way to identify when to use the decorator pattern is to determine if the new behavior is an option or not. If they are "options" where different combinations are allowed, then the decorator pattern is the way to go.
I am very hungry for some reason, so I am going to get going... Happy coding.