Click here to Skip to main content
15,885,032 members
Articles / General Programming / Architecture

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

Rate me:
Please Sign up or sign in to vote.
4.25/5 (12 votes)
10 Apr 2020CPOL3 min read 9.5K   88   8   4
This article demonstrates usage of Decorator pattern in C# with a practical software programming example.
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.

Using Inheritance

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:

Multiple Inheritance

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.

Subclassing

There are two problems with the above sub-classing approach:

  1. 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.
  2. 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.

Decorator pattern class diagram

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.

C#
// 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.

C#
// 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:

C#
// 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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Morgan Stanley
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionDisadvantages? Pin
Zeshan Munir29-May-20 12:53
Zeshan Munir29-May-20 12:53 
Questionthanks Pin
avisal4-May-20 9:01
professionalavisal4-May-20 9:01 
Questioncorrupted zip Pin
avisal11-Apr-20 6:47
professionalavisal11-Apr-20 6:47 
AnswerRe: corrupted zip Pin
Surajit T. Karmakar4-May-20 8:27
professionalSurajit T. Karmakar4-May-20 8:27 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.