Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Decorator Pattern explained

4.67/5 (17 votes)
28 May 2016CPOL3 min read 18.2K   108  
This article explains decorator pattern and its usage.

 

Introduction

This article explains decorator pattern, its usage and compares it with other ways of achieving the same result.

Background

Well, the defintion says "The decorator pattern is a design pattern that allows behavior to be added to an individual object dynamically without affecting the behavior of other objects from the same class." 
It also adhers to Single Responsibility and Open close principle.

Let's understand the definition with the help of code.

Using the code

A telecom organization 'xyz' hires a developer, John, to write an application to generate invoice for its customers in pdf format. John creates a class 'Invoice' with Generate() method in it.

C++
    public class Invoice
    {
        public PdfDocument Generate()
        {
            // Create a new PDF document
            PdfDocument document = new PdfDocument();

            // Create an empty page
            PdfPage pdfPage = document.AddPage();

            // Get an XGraphics object for drawing
            XGraphics graph = XGraphics.FromPdfPage(pdfPage);

            // Create a font
            XFont font = new XFont("Verdana", 20, XFontStyle.Bold);

            graph.DrawString("This is your bill....", font, 
                XBrushes.Black, new XRect(0, 0, pdfPage.Width.Point, pdfPage.Height.Point), 
                XStringFormats.Center);

            return document;
        }
    }

Now, his company says that application should add a water mark content in each page of invoice if customer has not paid the last month(s) bill. The watermark content may read something like this 'April & May bill' or 'March, April & May bill'. He changes the code as written below:

1) Adds a boolean property in this class 'Watermark'. If it is true, add the water mark content, else no water mark at all.

2) Add a parameterized construtor to accept the value of Watermark property.

3) Modifies the Generate method to add watermark logic based on this property value.

public class Invoice
   {
       public bool Watermark { get; private set; }
       public Invoice(bool waterMark)
       {
           Watermark = waterMark;
       }

       public PdfDocument Generate()
       {
           // Create a new PDF document
           PdfDocument document = new PdfDocument();

           // Create an empty page
           PdfPage pdfPage = document.AddPage();

           // Get an XGraphics object for drawing
           XGraphics graph = XGraphics.FromPdfPage(pdfPage);

           // Create a font
           XFont font = new XFont("Verdana", 20, XFontStyle.Bold);

           graph.DrawString("This is your bill....", font, XBrushes.Black, new XRect(0, 0, pdfPage.Width.Point, pdfPage.Height.Point), XStringFormats.Center);

           if (Watermark)
           {
               string waterMarkText = "April and May bill.";
               // Get the size (in point) of the text
               XSize size = graph.MeasureString(waterMarkText, font);

               // Define a rotation transformation at the center of the page
               graph.TranslateTransform(pdfPage.Width / 2, pdfPage.Height / 2);
               graph.RotateTransform(-Math.Atan(pdfPage.Height / pdfPage.Width) * 180 / Math.PI);
               graph.TranslateTransform(-pdfPage.Width / 2, -pdfPage.Height / 2);

               // Create a string format
               XStringFormat format = new XStringFormat();
               format.Alignment = XStringAlignment.Near;
               format.LineAlignment = XLineAlignment.Near;

               // Create a dimmed red brush
               XBrush brush = new XSolidBrush(XColor.FromArgb(128, 255, 0, 0));

               // Draw the string
               graph.DrawString(waterMarkText, font, brush,
                 new XPoint((pdfPage.Width - size.Width) / 2, (pdfPage.Height - size.Height) / 2),
                 format);

           }
           return document;
       }
   }

So far so good? No? what's wrong? Yes, John voilates the open close principle here. He modifes the existing code which may introduce bugs in existing functionality.

After consulting his technical architect, John takes a subclass approach. Instead of touching the existing Invoice class, he creates a child class of it, naming it as WatermarkedInvoice. 

public class Invoice
    {
        //Other common members used by child classes as well.
        public virtual PdfDocument Generate()
        {
            // Create a new PDF document
            PdfDocument document = new PdfDocument();

            // Create an empty page
            PdfPage pdfPage = document.AddPage();

            // Get an XGraphics object for drawing
            XGraphics graph = XGraphics.FromPdfPage(pdfPage);

            // Create a font
            XFont font = new XFont("Verdana", 20, XFontStyle.Bold);

            graph.DrawString("This is your bill....", font,
              XBrushes.Black, new XRect(0, 0, pdfPage.Width.Point, pdfPage.Height.Point),
              XStringFormats.Center);

            return document;
        }
    }

 

public class WatermarkedInvoice: Invoice
   {
       public override PdfDocument Generate()
       {
           // Create a new PDF document
           PdfDocument document = new PdfDocument();

           // Create an empty page
           PdfPage pdfPage = document.AddPage();

           // Get an XGraphics object for drawing
           XGraphics graph = XGraphics.FromPdfPage(pdfPage);

           // Create a font
           XFont font = new XFont("Verdana", 20, XFontStyle.Bold);

           graph.DrawString("This is your bill....", font,
             XBrushes.Black, new XRect(0, 0, pdfPage.Width.Point, pdfPage.Height.Point),
             XStringFormats.Center);

           string waterMarkText = "April and May bill.";
           // Get the size (in point) of the text
           XSize size = graph.MeasureString(waterMarkText, font);

           // Define a rotation transformation at the center of the page
           graph.TranslateTransform(pdfPage.Width / 2, pdfPage.Height / 2);
           graph.RotateTransform(-Math.Atan(pdfPage.Height / pdfPage.Width) * 180 / Math.PI);
           graph.TranslateTransform(-pdfPage.Width / 2, -pdfPage.Height / 2);

           // Create a string format
           XStringFormat format = new XStringFormat();
           format.Alignment = XStringAlignment.Near;
           format.LineAlignment = XLineAlignment.Near;

           // Create a dimmed red brush
           XBrush brush = new XSolidBrush(XColor.FromArgb(128, 255, 0, 0));

           // Draw the string
           graph.DrawString(waterMarkText, font, brush,
             new XPoint((pdfPage.Width - size.Width) / 2, (pdfPage.Height - size.Height) / 2),
             format);

           return document;
       }
   }

Now, his organisation says that application should show the red colored border if customer's credit limit has already been crossed. The possible scenarios can be:

1) Customer crosses the credit limit in one month itself. So the invoice will have only red colored border but no water mark content.

2) Customer hasn't paid the last month bill and crosses the credit limit in current month. Invoice will have red colored border and water mark content both.

3) Customer hasn't paid the last month bill but usage is still under credit limit. Invoice will only have water mark content.

4) Customer has paid the last moth bill and current month usage is under credit limit. Invoice will neither have water mark content nor red colored border.

If John to continue with subclasses approach, he will end up creating following classes:

1) WatermarkedInvoice

2) RedColoredBorderInvoice

3) WatermarkAndRedColoredBorderInvoice

Imagine the  number of classes if organisation adds more business logic which are independent of each other and based on that John needs to show any visual marks. 

 

So, what's the solution? Decorator pattern as it allows behavior to be added to an individual object dynamically without affecting the behavior of other objects.

 

Decorator pattern creates/designs a base first and then decorate it accordingly. E.g.

1) Pizza, It has a base and all its toppings such as onion, mushroom, capsicum, tomato are decorating components i.e. Decorators. You may decorate pizza base with any number of decorating components.

2) Burger: A bun (base) decorated with Cheese, Veggies, Bacon etc. (decorators)

Similarly, in our current example, a basic invoice can be considered as base and watermark or red colored border are decorators.

In order to follow this design pattern, John creates following items:

Component (IInvoice): An interface implemented by both, base and decorators.

//Component 
public interface IInvoice 
{ 
     PdfDocument Generate(); 
}

 

ConcreateComponent (Invoice): A base which needs to be decorated.

//Concreate component
  public class Invoice : IInvoice
    {
        public PdfDocument Generate()
        {
            // Create a new PDF document
            PdfDocument document = new PdfDocument();

            // Create an empty page
            PdfPage pdfPage = document.AddPage();

            // Get an XGraphics object for drawing
            XGraphics graph = XGraphics.FromPdfPage(pdfPage);

            // Create a font
            XFont font = new XFont("Verdana", 20, XFontStyle.Bold);

            graph.DrawString("This is your bill....", 
            font, XBrushes.Black, 
            new XRect(0, 0, pdfPage.Width.Point, pdfPage.Height.Point), XStringFormats.Center);

            graph.Dispose();

            return document;
        }
    }

Decorator(InvoiceDecorator): A base class for all decorators.

public abstract class InvoiceDecorator:IInvoice
   {
       protected IInvoice _invoice;

       public InvoiceDecorator(IInvoice invoice)
       {
           _invoice = invoice;
       }
       public virtual PdfDocument Generate()
       {
           PdfDocument invoice=null;
           if (_invoice != null)
               invoice = _invoice.Generate();
           return invoice;
       }
   }

ConcreateDecoratorA(DecoratedWithWatermark): A decorator.

 

public class DecoratedWithWatermark : InvoiceDecorator
    {
        public DecoratedWithWatermark(IInvoice invoice) : base(invoice)
        {
            _invoice = invoice;
        }
        public override PdfDocument Generate()
        {
            PdfDocument invoice = base.Generate();
            invoice = AddWatermark(invoice);
            return invoice;
        }
        private PdfDocument AddWatermark(PdfDocument invoice)
        {
            PdfPage page = invoice.Pages[0];
            string watermark = "April and May bill.";
            XFont font = new XFont("Verdana", 20, XFontStyle.Bold);
            // Get an XGraphics object for drawing beneath the existing content
            XGraphics gfx = XGraphics.FromPdfPage(page, XGraphicsPdfPageOptions.Prepend);

            // Get the size (in point) of the text
            XSize size = gfx.MeasureString(watermark, font);

            // Define a rotation transformation at the center of the page
            gfx.TranslateTransform(page.Width / 2, page.Height / 2);
            gfx.RotateTransform(-Math.Atan(page.Height / page.Width) * 180 / Math.PI);
            gfx.TranslateTransform(-page.Width / 2, -page.Height / 2);

            // Create a string format
            XStringFormat format = new XStringFormat();
            format.Alignment = XStringAlignment.Near;
            format.LineAlignment = XLineAlignment.Near;

            // Create a dimmed red brush
            XBrush brush = new XSolidBrush(XColor.FromArgb(128, 255, 0, 0));

            // Draw the string
            gfx.DrawString(watermark, font, brush,
              new XPoint((page.Width - size.Width) / 2, (page.Height - size.Height) / 2),
              format);

            gfx.Dispose();

            return invoice;
        }
    }

ConcreateDecoratorB (DecoratedWithRedColoredBorder): Another decorator

 

public class DecoratedWithRedColoredBorder : InvoiceDecorator
    {
        public DecoratedWithRedColoredBorder(IInvoice invoice) : base(invoice)
        {
            _invoice = invoice;
        }
        public override PdfDocument Generate()
        {
            PdfDocument invoice = base.Generate();
            invoice = AddBorder(invoice);
            return invoice;
        }
        private PdfDocument AddBorder(PdfDocument report)
        {
            PdfPage page = report.Pages[0];
            XFont font = new XFont("Verdana", 20, XFontStyle.Bold);

            // Get an XGraphics object for drawing beneath the existing content
            XGraphics gfx = XGraphics.FromPdfPage(page, XGraphicsPdfPageOptions.Prepend);

            // Adds a border

            XPen pen = new XPen(XColors.Red, Math.PI);
            
            gfx.DrawRectangle(pen, XBrushes.White, 10, 10, page.Width - 20, page.Height - 20);

            gfx.Dispose();
            return report;
        }
    }

By doing this, John is able to add behavior dynamically e.g. adding watermark and RedColoredBorder indenpendently.

Client class looks like this:

Create the invoice object first and then injecting this object in decorated objects based on user inputs.

UI has 2 check boxes, one for water mark and other for red colored border. Depending on what the user chooses, base invoice will be decorated accordingly.

  IInvoice invoice = new DecoratorPattern.UsingDecoratorPattern.Invoice();
            foreach (var checkedItem in checkedListBox1.CheckedItems)
            {
                if (String.Compare(checkedItem.ToString(), "Watermark", true) == 0)
                {
                    invoice = new DecoratedWithWatermark(invoice);
                }
                if (String.Compare(checkedItem.ToString(), "Border", true) == 0)
                {
                    invoice = new DecoratedWithRedColoredBorder(invoice);
                }
            }

            PdfDocument document= invoice.Generate();
            string pdfFilename = "DecoratedDesignPatternExplained.pdf";
            string path = Path.Combine(
                          System.IO.Path.GetDirectoryName(
                             System.Reflection.Assembly.GetEntryAssembly().Location)
                             , pdfFilename
                             );
            document.Save(path);
            this.textBox1.Text = String.Format("{0}: PDF generated at {1}", DateTime.Now.ToShortTimeString(), path);

I hope this examples shows how strong a decorator pattern can be in a situation like this.

Your comments/feedback are most welcome. Let's grow by learning together.

When you build the project, please make sure you have reference added for pdfsharp library.

History

First version.

License

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