Introduction
This article talks about the basics of decorator pattern and see when this pattern can be found useful.
We will also work on a rudimentary implementation of Decorator Pattern in C#.
Background
There are some occasions in our applications when we need to create an object with some basic functionality in such
a way that some extra functionality can be added to this object dynamically. For example, Lets say we need to create a
Stream object to handle some data but in some cases we need this stream object to be able to encrypt the stream in
some cases. So what we can do is that we can have the basic Stream object ready and then dynamically add the encryption
functionality when it is needed.
One may also say that why not keep this encryption logic in the stream class itself and turn it
on or off by using a Boolean property. But this approach will have problems like - How can
we add the type custom encryption logic inside a class? Now this can be done easily by
subclassing the existing class and have custom encryption logic in the derived class.
This is a valid solution but only when this encryption is the only functionality needed with this class. But what if
there are multiple functionalities that could be added dynamically to this class and also the combination
of functionalities too. If we use the subclassing approach then we will end up with derievd classes equal to
the number of combination we could have for all our functionalities and the actual object.
This is exactly the scenario where the decorator patter can be useful. GoF defines Decorator pattern as
"Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative
to subclassing for extending functionality."
Before looking into the details of decorator pattern let us go ahead and see the class diagram of this
pattern and see what each class is responsible for.
Component
: It defines the interface of the actual object the need fucntionalities to be
added dynamically to the ConcreteComponents
.ConcreteComponent
: The actual object in which the functionalities could be added dynamically.Decorator
: This defines the interface for all the dynamic functionalities that can be added to the
ConcreteComponent
ConcreteDecorator
: All the functionalities that can be added to the ConcreteComponent
. Each needed
funcitonality will be one ConcreteDecorator
class.
Using the code
To understand the decorator pattern let us look at an example of a billing system of a Bakery. This
Bakery specializes in Cakes and Pastries. Customers can buy cakes and pastries and then choose to have
extra stuff added on the base product. The extra Product are Cream, Cherry, Scent and Name card.
Now if we take the classic approach of subclassing to create this billing system we will end up with classes
like
<li>CakeWithCreamAndCherry</li>
<li>CakeWithCreamAndCherryAndScent</li>
<li>CakeWithCreamAndCherryAndScentAndNameCard</li>
<li>CakeWithCherryOnly</li>
<li>PastryOnly</li>
<li>PastryWithCreamAndCherry</li>
<li>PastryWithCreamAndCherryAndScent</li>
<li>PastryWithCreamAndCherryAndScentAndNameCard</li>
<li>PastryWithCherryOnly </li>
AND MANY MANY MORE..............
Not only this approach is a development problem this is a maintenance nightmare. creating and maintaining these sets of
classes will be a very big problem. So let us see how we can elegantly design this solution by using Decorator Pattern.
Let us start by creating the Component
interface.
public abstract class BakeryComponent
{
public abstract string GetName();
public abstract double GetPrice();
}
This class defines the interface of the actual object the need functionalities to be
added dynamically to ConcreteComponents
. So let us now create the ConcreteComponent
classes.
class CakeBase : BakeryComponent
{
private string m_Name = "Cake Base";
private double m_Price = 200.0;
public override string GetName()
{
return m_Name;
}
public override double GetPrice()
{
return m_Price;
}
}
class PastryBase : BakeryComponent
{
private string m_Name = "Pastry Base";
private double m_Price = 20.0;
public override string GetName()
{
return m_Name;
}
public override double GetPrice()
{
return m_Price;
}
}
Now we have our base object ready. Now we will look into how the other required things can be added to it
dynamically. Lets start by looking at the Decorator
class.
public abstract class Decorator : BakeryComponent
{
BakeryComponent m_BaseComponent = null;
protected string m_Name = "Undefined Decorator";
protected double m_Price = 0.0;
protected Decorator(BakeryComponent baseComponent)
{
m_BaseComponent = baseComponent;
}
#region BakeryComponent Members
string BakeryComponent.GetName()
{
return string.Format("{0}, {1}", m_BaseComponent.GetName(), m_Name);
}
double BakeryComponent.GetPrice()
{
return m_Price + m_BaseComponent.GetPrice();
}
#endregion
}
There are two things to notice here. First that this class implements the BakeryComponent
interface. The reason
for that is a Cake with a Component will also be a cake and thus all the operations possible on a cake should also
be possible on a Decorated cake. The second interesting thing to note is that it also hold the
BakeryComponent
object inside. The reason for that is that we need the logical is-a relationship between a cake and a decorating
item but since actually that is not the case we hold a
BakeryComponent
object inside to be able to mimic that is-a
relationship.
In short what we have done is that instead of having a static is-a relationship using inheritance, we have
a dynamic is-a relationship by using composition.
Let us now see how the ConcreteDecorators
can be implemented.
class ArtificialScentDecorator : Decorator
{
public ArtificialScentDecorator(BakeryComponent baseComponent)
: base(baseComponent)
{
this.m_Name = "Artificial Scent";
this.m_Price = 3.0;
}
}
class CherryDecorator : Decorator
{
public CherryDecorator(BakeryComponent baseComponent)
: base(baseComponent)
{
this.m_Name = "Cherry";
this.m_Price = 2.0;
}
}
class CreamDecorator : Decorator
{
public CreamDecorator(BakeryComponent baseComponent)
: base(baseComponent)
{
this.m_Name = "Cream";
this.m_Price = 1.0;
}
}
Now in these classes we have simply set the decorator specific values of the items and not customized
any behavior. But if we want we can even customize the behavior or add more state variables in the
ConcereteDecorator
objects. To illustrate this point lets say whenever the customer choose to add the Namecard on his cake he is eligible to
get a discount card for the next purchase and we need to show this message in the receipt. Lets see how the
ConcreteDecorator
will add its own state and behavior in that case.
class NameCardDecorator : Decorator
{
private int m_DiscountRate = 5;
public NameCardDecorator(BakeryComponent baseComponent)
: base(baseComponent)
{
this.m_Name = "Name Card";
this.m_Price = 4.0;
}
public override string GetName()
{
return base.GetName() +
string.Format("\n(Please Collect your discount card for {0}%)",
m_DiscountRate);
}
}
Now our client application can create combination of these ConcreteComponents
with any Decorator
. Lets look
at the sample code implementation for the client.
static void Main(string[] args)
{
CakeBase cBase = new CakeBase();
PrintProductDetails(cBase);
CreamDecorator creamCake = new CreamDecorator(cBase);
PrintProductDetails(creamCake);
CherryDecorator cherryCake = new CherryDecorator(creamCake);
PrintProductDetails(cherryCake);
ArtificialScentDecorator scentedCake = new ArtificialScentDecorator(cherryCake);
PrintProductDetails(scentedCake);
NameCardDecorator nameCardOnCake = new NameCardDecorator(scentedCake);
PrintProductDetails(nameCardOnCake);
PastryBase pastry = new PastryBase();
PrintProductDetails(pastry);
CreamDecorator creamPastry = new CreamDecorator(pastry);
CherryDecorator cherryPastry = new CherryDecorator(creamPastry);
PrintProductDetails(cherryPastry);
}
And when we run the application.
Before wrapping up lets look at how our sample application is implementing the decorator pattern in terms of
class diagram and lets compare it with the class diagram of decorator pattern.
Point of Interest
In this introductory article we have looked into the decorator pattern. When can it be useful and
how can we implement the decorator pattern in C#. Decorator pattern is a very good example of
Open-Closed
principle
where all our classes are Open for extension but closed for modification. I hope this has been
informative.
History
- 19 October 2012: First version.