This article is more focused on why Decorator pattern exists in the first place. What is the problem if this pattern is not used? And how does this pattern solve the problem? All these details are demonstrated with a practical software programming example in C#.
Background
GoF Definition: Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
This pattern supports the Open-Closed principle of SOLID which means you can add new functionality to an existing class without modifying it. You can add a new functionality to an existing class either using inheritance or composition.
Let us consider a practical software programming example.
I have an invoice module in my application which prints a simple Customer Invoice. This invoice contains text data like customer name, address, order no, order amount & order date. This functionality was used by several of my customers over years.
Now, one of my clients (Client A) wants to print invoice with Colors. The second client (Client B) wants to print invoice with Headers. The third client (Client C) wants to print invoice with Footers. So, how do you think I can cater to this requirement? Inheritance? Let's try that.
With inheritance, I will end up with the above classes. I will create a subclass for each of the new functionality. It seems fine until now. But the above scenario will create complexity when a new client (Client D) wants to print invoice with Header & Footer together. If we want to achieve Client D's requirement, our structure might look like this:
Oh wait, this is Multiple inheritance. I can't do this in C#. I can't implement from the above two classes. So, now we will have to create a new Subclass which will print invoice with headers and footers and will cater to Client D's requirement.
There are two problems with the above sub-classing approach:
- You end up with many subclasses. Precisely, one subclass for each combination like Headers + Footers is one combination. Another combination can be Color +Headers... and so on. In a large scale system, maintaining and debugging lot of sub classes will be difficult.
- If you follow the Single Responsibility Principle of SOLID, it states that a class should be dealing with a single part of functionality. So, our subclass should not be dealing with a combination of tasks. This means one class should be adding colors while another should be adding header information.
To overcome the above problems, Decorator pattern is used.
Using Decorator pattern, you attach a new functionality without affecting the existing classes. And it offers an alternative to subclassing for extending existing classes.
In the above example, InvoiceBase
and Invoice
classes are the existing classes. Any new functionality that I need to add will be a Decorator
. I can attach multiple decorators to our existing classes without creating individual sub-classes for each combination. I created an abstract
class InvoiceDecorator
and three additional classes, ColorDecorator
, HeaderDecorator
and FooterDecorator
. InvoiceDecorator
has a composition of InvoiceBase
object. When we want to add new functionalities to the existing functionality, we use AttachTo
method. The idea behind this is to add a new decorator in future without affecting my existing Invoice
class.
I have created a sample application which simulates the invoice printing operation.
Using the Code
Following are my existing classes which won't be modified while adding a new functionality.
abstract class InvoiceBase
{
protected static string data;
public abstract void CreateInvoice();
public void PrintInvoice()
{
Console.WriteLine(data);
}
public void Reset()
{
data = string.Empty;
}
}
public class Invoice : InvoiceBase
{
public override void CreateInvoice()
{
data += "\n";
data += "\tCustomer Name : Chris\n";
data += "\tCustomer Address : Edmond Road, NJ\n";
data += "\tOrder No : 1254158\n";
data += "\tOrder Amount : Rs. 2540/- \n";
data += "\tOrder Date : 09-Apr-2020 \n";
data += "\n";
}
}
Following are the Decorator
classes which are decoupled with the concrete implementation. We can add any number of Decorators for extending functionalities. Also, note that, I have created a single class for one responsibility as per SOLID principles, i.e., Color handling is taken care by ColorDecorater
and Header info handling is taken care by HeaderDecorator
class.
abstract class InvoiceDecorator : InvoiceBase
{
InvoiceBase invoiceBase;
public void AttachTo(InvoiceBase invoice)
{
this.invoiceBase = invoice;
}
public override void CreateInvoice()
{
invoiceBase.CreateInvoice();
}
}
class ColorDecorator : InvoiceDecorator
{
public override void CreateInvoice()
{
AddColor();
base.CreateInvoice();
}
private void AddColor()
{
Console.ForegroundColor = ConsoleColor.Green;
}
}
class HeaderDecorator : InvoiceDecorator
{
public override void CreateInvoice()
{
AddHeader();
base.CreateInvoice();
}
private void AddHeader()
{
string header = "\n\tBlue Heaven Inc.\n"
+ "\tBay Area, NC\n"
+ "\t+1 784251485\n\n";
data = header + data;
}
}
class FooterDecorator : InvoiceDecorator
{
public override void CreateInvoice()
{
base.CreateInvoice();
AddFooter();
}
void AddFooter()
{
string footer = "\n\tCopyright @ 2020.All rights reserved\n";
data += footer;
}
}
The client code will look like the following:
InvoiceBase invoice = new Invoice();
invoice.CreateInvoice();
invoice.PrintInvoice();
InvoiceBase invoice = new Invoice();
InvoiceDecorator colorDecorator = new ColorDecorator();
colorDecorator.AttachTo(invoice);
colorDecorator.CreateInvoice();
invoice.PrintInvoice();
InvoiceBase invoice = new Invoice();
InvoiceDecorator colorDecorator = new ColorDecorator();
InvoiceDecorator headerDecorator = new HeaderDecorator();
InvoiceDecorator footerDecorator = new FooterDecorator();
colorDecorator.AttachTo(invoice);
footerDecorator.AttachTo(colorDecorator);
headerDecorator.AttachTo(footerDecorator);
headerDecorator.CreateInvoice();
invoice.PrintInvoice();
Points of Interest
Many a times while working on client requirements, you don't have the complete requirement and you develop incrementally. This pattern can be used in such scenarios. You can add Decorators as per new requirements thereby ensuring that your Base class is not complex in the first place.
History
- 10th April, 2020 - Initial version