Decorator Pattern in C# Explained with a Practical Software Programming Example






4.28/5 (11 votes)
This article demonstrates usage of Decorator pattern in C# with a practical software programming example.
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.
// This is the base abstract class which is inherited
// by all concrete and decorator classes
abstract class InvoiceBase
{
// string data stores the content which it to be printed in the invoice.
// This variable will be altered by the subclasses. Thus, it is marked protected.
protected static string data;
// This function will be implemented in all the concrete classes as well as decorators
public abstract void CreateInvoice();
public void PrintInvoice()
{
Console.WriteLine(data);
}
//This function clear the variable value after invoice is printed.
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.
// This is the base Decorator class which has the composition, i.e., InvoiceBase object
// AttachTo method is used to attach responsibility dynamically through client code.
abstract class InvoiceDecorator : InvoiceBase
{
InvoiceBase invoiceBase;
public void AttachTo(InvoiceBase invoice)
{
this.invoiceBase = invoice;
}
public override void CreateInvoice()
{
invoiceBase.CreateInvoice();
}
}
//These are individual decorator classes.
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:
// CASE 1: This is the existing implementation to print a simple invoice.
InvoiceBase invoice = new Invoice();
invoice.CreateInvoice();
invoice.PrintInvoice();
// CASE 2: Add color to the invoice
InvoiceBase invoice = new Invoice();
InvoiceDecorator colorDecorator = new ColorDecorator();
colorDecorator.AttachTo(invoice);
colorDecorator.CreateInvoice();
invoice.PrintInvoice();
// CASE 3: Add Color, Header and Footer to the invoice
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