Page 1 of 1

The Decorator Design Pattern

#1 [email protected]  Icon User is offline

  • D.I.C Addict
  • member icon

Reputation: 1003
  • View blog
  • Posts: 975
  • Joined: 30-September 10

Posted 30 January 2011 - 04:32 AM

Hello all and welcome to my tutorial on the Decorator Design Pattern!

What is a Design Pattern?

For those that are unsure on the concept, a Design Pattern is a solution to a common software design problem. Certain software design problems continually pop up during development of different applications, and Design Patterns give you a general solution to such problems so that you don’t have to completely invent a solution from scratch.

When thinking about a design pattern, think of it in terms of class libraries rather than standalone applications. Imagine you are building a class library using the pattern that many other people will use and write code against (like Microsoft made the classes in the System.IO namspace like StreamReader etc for you to use and code against, for example). This will help you appreciate the advantages.

Formal Definition of The Decorator Design Pattern

The Decorator Pattern attaches additional responsibilities to an object dynamically (at run time). Decorators provide a flexible alternative to sub-classing for extending functionality.

An Example

I am sure you have all had experience of the websites that allow you to ‘build your own computer’, and ‘spec out’ your computer just as you wanted. Say I want to build a very simple class hierachy that provides a similar functionality for my end users (similar...but much simplified!). I am going to build everything into a console application for clarity.

This presents me with a problem. There are a large number of different combinations of ways a user can ‘decorate’ their computer. For example, they can add a CD Drive, a monitor, an extra USB port, a mouse, a keyboard etc. I have no way of knowing what any of my users will choose in advance.

There is also a variety of different types of computer (laptop, desktop etc) a user could go for.

So, if we have a ‘Computer’ object (whether it be a laptop, desktop etc), in my simplistic example, we want to add responsibilities to the object (not the Computer class). We are ‘decorating’ the computer object with different features (USB Ports, CD Drives, Keyboards etc).

So, basically, we want the user to choose a type of computer out of two choices; laptop and desktop. We then want to give the user the option to decorate their choice of computer with additional features, like monitors, CD Drives etc. We should also be able to total up the cost of such additions.

So, how would you allow the users to add these different ‘features’ to the computer of their choice at their leisure?

Your first thought may well be inheritance.

A snippet of a class diagram for an inheritance hierarchy for a Desktop PC and all its features may look something like this:

Attached Image

Firstly, this is only a snippet of the full hierarchy, and as such doesn’t include all the combinations of features the user may want. You have to make objects that cover every possible combination of components. I am sure you can see how this will get very complex, very messy and very hard to maintain.


If a user wanted to add a CD Drive only to their computer, for example, a much more flexible approach would be to wrap the computer object with a CD Drive. In this instance, the Computer object is called the Component and the CD Drive object is called the Decorator. We are decorating the computer object with a CD Drive.

So, here is the full class diagram using the Decorator Design pattern (I shall explain fully what is going on afterwards) Notice how the full class diagram is much more concise and much neater than just a snippet of the static inheritance diagram previously shown:

Attached Image

Notice how the decorators (CDDrive, Keyboard etc) conform to the same polymorphic interface (the Computer abstract class, in this case) as the components (laptop and desktop computers). This is very important as it means that when we do wrap the computer objects with the decorators, the decorators are ‘transparent.’ As far as the client code is concerned, it is talking to a Computer object. It has no idea at all (they are loosely-coupled) what decorators are decorating the computer object, or indeed if there are any or not. This is made possible through the principle of polymorphism due to the fact that both decorators and components derive from the same abstract class, and so can be treated as being the same type (the type of the abstract class, i.e. Computer). Polymorphism is for another tutorial however.

This ‘transparency’ is very powerful. It means that the user can add any component they want, in any order. It also means that you as the developer can very easily add new ‘decorators’ that will give your users new features to choose from. All that would be required is that you create a decorator class representing the feature you want and have it implement the Computer decorator interface. It is then free to be added to a Computer object by the user.

You now have a very flexible, very extendable and very maintainable class structure, of which allows your users to add components to their computers at their leisure.

Let’s now move on to some very simplistic code to demonstrate this pattern in action.

Before we move on, here is a generic class diagram showing the standard features of the Decorator Pattern and who they usually fit together (taken from Head First: Design Patterns):

Attached Image

In our example;

Component = The abstract class that relates different types of computer
ConcreteComponent = the thing we are decorating and adding features to. So, in our case, we have two ConcreteComponents; Laptop and Desktop.
Decorator = the abstract class that relates the Decorators (or features) we are going to add to our ConcreteComponents (computers). Notice how Decorator implements the ConcreteComponent abstract class. This allows the decorators to be ‘invisible’ to client code. We’ll speak more about this later…

ConcreteDecoratorA/B = the features we are adding to the components (computers). In our case, these include a monitor, a keyboard, a CD Drive etc.

Onto the code:

First we shall create a Computer abstract class. This class, in generic terms, is the thing that ties all of our ConcreteComponents together (remember, ConcreteComponents are the laptop and desktop computers).

    /// <summary>
    /// This is a very important class. It provides a common interface
    /// to which all decorators and components should conform.
    /// All decorators and types of computer derive from this 
    /// abstract class. This is what allows decorators to be transparent to 
    /// client code as it means the decorators can be treated as a Computer
    /// type, and so too can the different types of computer (components)
    /// This class should be lightweight, i.e. it shouldn't focus on storing
    /// data. It should only focus on providing an interface for the other classes.
    /// If this class is heavyweight, you should perhaps look into the strategy design
    /// pattern instead.
    /// </summary>
    /// 

    public abstract class Computer
    {
         public abstract String Description();
         public abstract double Cost();
    }




This abstract class provides the basic outline for the Computer sub classes (Laptop and Desktop). For example, we are going to have a Laptop Class and a Desktop Class to represent different types of computers available. It ties the two together and makes them implement Description() and Cost(). This is important as those are the two characteristics of the computer we will be ‘modifying’ with our decorators.

Next, we make the ComputerDecorator abstract class. In generic terms, this is the abstract class that ties all the decorators (Keyboard, USB Port, Monitor etc) together. In our generic diagram above, it is equivalent to the Decorator abstract class. It ensures they all implement the required methods etc, and provides the basic skeleton of the decorator classes.

    /// <summary>
    /// As we have more than one decorator, 
    /// we use this abstract class to define the characteristics of
    /// the decorators, in order to tie them all together
    /// </summary>

    public abstract class ComputerDecorator : Computer
    {
        protected Computer computer = null;

        public override String Description()
        {
            return computer.Description();  
        }

        public override Double Cost()
        {
            return computer.Cost();  
        }
    }




Firstly, notice how this class derives from the Computer. This means that, as all decorators will eventually derive from this class, all decorators will be of type Computer. Therefore, they can all be treated as Computer objects. This will help us achieve the transparency spoken about previously and will help us when it comes to maintenance and extending the code base. Again, more on this a little later…

Secondly, notice how we hold a reference to a Computer object. Remember that everything in this class will be passed down to the sub classes (i.e. the decorators) (which is why we used the ‘protected’ keyword rather than the ‘private’ keyword, as we want that variable to be passed down to sub classes). Therefore, every decorator object will have access to an object of type Computer and will have access to newly implemented Description and Cost methods. This all will become clearer soon.

Now, we implement the classes modelling the different types of computer (Laptop and Desktop in our case). In our generic diagram, these classes represent the ConcreteComponents. These are the things we are decorating with new ‘features.’

Laptop Class

    /// <summary>
    /// Represents a Laptop Computer - A COMPONENT
    /// </summary>
    /// 

    public class Laptop : Computer
    {
        //override the method provided by the Computer abstract class and return the relevant string
        //of which represents the most basic, bog standard laptop computer
        public override String Description()
        {
            return "Laptop Computer";
        }
        //override the method provided by the Computer abstract class and return price of basic 
        //laptop computer
        public override double Cost()
        {
            return 399.99;
        }
    }




Desktop class

/// <summary>
    /// Represents a Desktop Computer - a COMPONENT
    /// </summary>

    public class Desktop : Computer
    {
        //override the method provided by the Computer abstract class and return the relevant string
        //of which represents the most basic, bog standard Desktop computer
        public override String Description()
        {
            return "Desktop Computer";
        }
        //override the method provided by the Computer abstract class and return price of basic 
        //desktop computer
        public override double Cost()
        {
            return 499.99;
        }
    }




Notice how both classes inherit from the Computer abstract class as both classes need to provide their own implementation of Description() and Cost() (the default implementation with no features added for both types of computer). So, we have a plain old Desktop computer and a plain old Laptop computer. This is good! We have the starting point for our application. Every user will start off with either a laptop or desktop PC only (or whatever other types of computer we could have included). They will then add features to the computers at their leisure.

Therefore, we just return the price and description of a standalone laptop (imagine the laptop has no features at all, it is basically just the CPU alone; strange as that may be, but humour me ) and a desktop pc. Great!

Now for the final part. The decorators themselves. This is where much of the functionality associated with the Decorator Pattern happens. In terms of our generic example, the decorators (USB Port, Monitor etc) are the ConcreteDecorators. They are the feature we are going to add to our bare, bog standard computers.

In the interest of time and space, I shall show only the CDDrive and Mouse classes, just so you get the general idea:

CDDrive Class

    /// <summary>
    /// Represents a CD Drive - A DECORATOR
    /// </summary>
    /// 

    public class CDDrive : ComputerDecorator
    {
        //assign Computer object to computer variable inherited
        //from ComputerDecorator abstract class
        public CDDrive(Computer computer)
        {
            this.computer = computer;
        }


        //provide custom implementation of Description method
        public override String Description()
        {
            //check to ensure a Computer object is loaded into the 'computer' variable
            //call the Description method of the Computer object reference by the 
            //'computer' variable
            if (this.computer != null) return base.Description() + " and a CD Drive";
            //if the conditional fails, return an empty strign as there is no 
            //Computer object to work with
            return String.Empty;
        }

        //calculate the cost in the same way as we
        //get the description
        public override double Cost()
        {
            return 30 + base.Cost();
        }
    }




Mouse Class

  /// <summary>
    /// Represents a Mouse - A DECORATOR
    /// </summary>
    /// 
    class Mouse : ComputerDecorator
    {   
        //assign Computer object to computer variable inherited
        //from ComputerDecorator abstract class
        public Mouse(Computer computer)
        {
            this.computer = computer;
        }

        //provide custom implementation of Description method
        public override String Description()
        {
            //check to ensure a Computer object is loaded into the 'computer' variable
            //call the Description method of the Computer object reference by the 
            //'computer' variable
            if (computer != null) return base.Description() + " and a Mouse";
            //if the conditional fails, return an empty strign as there is no 
            //Computer object to work with
            return String.Empty;
        }

        //calculate the cost in the same way as we
        //get the description
        public override double Cost()
        {
            if (this.computer != null) return 24.99 + base.Cost();

            return 0;
        }
    }


Firstly, notice how both classes derive from the ComputerDecorator abstract class. This means that they have inherited the variable holding a reference to the Computer object. We pass a Computer object of to the constructor (remember: due to polymorphism, this means we can pass either a Laptop object OR a Desktop object to these decorators, as both the Desktop and Laptop classes derive from the Computer abstract class. However, crucially, it means the constructors can accept decorator objects also (Keyboard, Monitor etc), as, remember, these object ultimately derive from the Computer abstract class too. This is the first glimpse at the power of polymorphism and using abstract classes and interfaces. The Computer object we pass to the decorators is, initially, the Computer object we want ‘decorating’ with new features. We see how the computer objects get wrapped and passed around soon.

Then, we override Description() and Cost() once again! This is because we want to provide a different implementation as, this time; we want to add something to the Computer object.

In the description method, firstly we check to ensure that the variable referencing the computer object is not null. If it is null, it means we haven’t got a Computer object to decorate (add new feature too), so we return an empty string only. If it isn’t null however (i.e. there is a Computer object that need decorating), we firstly call the Description() method of the base class (the class that the CDDrive and Mouse Classes derive from, which is the abstract class called ComputerDecorator. We delegate the responsibility of producing the already built description. By delegating to the base class, it means, amongst other things, future changes are made easier as you will have few classes to change.

Remember, the description method in the abstract class calls the description method of the current Computer object (it delegates responsibility to the Computer object). We then concatenate the appropriate string onto the end of this already built string to show that a feature has been added to the Computer object. I shall explain this process in more detail in a minute.

We do much the same with Cost(), we return the cost so far added onto the cost of the decorator feature. So, in our CDDrive class, we add the cost of the CDDrive (£30), to the total cost of the features already added.

So, basically, these decorators add their individual bits and pieces onto the Computer objects. In this way, by been passed around these decorators, the current Computer object gets features added to it.

Now, all that is left to do is tie it all together in Main(). We are going to decorate a laptop computer and a desktop computer. We are also going to create a laptop computer and not decorate it at all. Just too ensure we still get the correct output.


   class Program
    {
        static void Main(string[] args)
        {
            
            Computer laptopComputer = new Laptop(); //create a bog standard laptop computer
            Computer desktopComputer = new Desktop();//create the most basic of desktop computers
            Computer laptopComputer2 = new Laptop();

            //decorate the latpop with new features

            laptopComputer = new CDDrive(laptopComputer);
            laptopComputer = new USBPort(laptopComputer);
            laptopComputer = new USBPort(laptopComputer);
            laptopComputer = new Mouse(laptopComputer);

            //decorate thedesktop computer with new features

            desktopComputer = new Monitor(desktopComputer);
            desktopComputer = new Keyboard(desktopComputer);
            desktopComputer = new USBPort(desktopComputer);
            desktopComputer = new Mouse(desktopComputer);
            desktopComputer = new CDDrive(desktopComputer);
            desktopComputer = new CDDrive(desktopComputer);
            
            //print out different computers with their new features added to test to ensure features have been added and the total cost calculated
           
            Console.WriteLine("The first order was for a...\n" + laptopComputer.Description()+"\n"+ "At a total cost of: " + laptopComputer.Cost().ToString("C") + ".");
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("The second order was for a...\n" + desktopComputer.Description() + "\n" + "At a total cost of: " + desktopComputer.Cost().ToString("C") + ".");
            Console.WriteLine(Environment.NewLine);
            //There may be a user that doesn't wnat any of the features!
            Console.WriteLine("The third order was for a...\n" + laptopComputer2.Description() + "\n" + "At a total cost of: " + laptopComputer2.Cost().ToString("C") + ".");
            Console.ReadLine();
        }
    }




The output is:

Attached Image

So, we can see that this works like a dream! However, how exactly does it work, I here you cry?

Well, I shall go through the decorating of the desktop computer’s description step by step below (the Cost method is exactly the same principle):

1. We create a new Desktop Computer object. This represents the most basic computer, of which has no additional features attached to it.

2. We then pass this object to the Monitor Class constructor (we wrap the Desktop object with the Monitor object). The object gets stored in the internal variable called computer. The description method is called on that stored computer object using the ‘base’ keyword. This returns “Desktop Computer” as that variable is holding a reference to the Desktop object just passed into the constructor (the Desktop object’s Description method returns “Desktop Object”, of course). We then append “and a Monitor” onto the end in the Monitor object’s Description method.

3. This Monitor object, of which is returning “Desktop Computer and a Monitor” from its description method, is stored (or, a reference to that object is stored at least) in the desktopComputer variable in our main Program class. Remember, we are allowed to store the decorator objects in a variable of type Computer because each decorator class derives from the Computer abstract class, and so can be treated as a Computer object.

4. Next, we pass the reference to Monitor object (the desktopComputer variable is now storing this reference) to a Keyboard object. So, we are passing an object that is already returning “Desktop Computer and a Monitor”, to the Keyboard object.

5. This Monitor object is stored in the computer variable of the Keyboard class.

6. When we call the Description method now, base.Description() in the Keyboard object will return “Desktop Computer and a Monitor”, Remember, base.Description() is working on the stored computer variable. Now however, the stored computer variable doesn’t hold a reference to a Desktop Computer object, it holds a reference to a Monitor object. Now, if you remember, the Monitor object is returning “Desktop Computer and a Monitor”. Therefore, base.Description() returns “Desktop Computer and a Monitor” as all it is doing is calling the Description method of the Monitor object. So, the keyboard object is now returning “Desktop Computer and a Monitor and a Keyboard” as “and a Keyboard” gets appended onto the string in the Keyboard object’s Description method.

7. This newly configured Keyboard object is stored in the desktopComputer variable. It is returning “Desktop Computer and a Monitor and a Keyboard”.

This is how the wrapping works. It just continues on in this way for all the wrappers (decorator objects) in the example. Basically, when we eventually call desktopComputer.Description(), we are calling it on a CDDrive object (as that is the last wrapper we wrap the desktopComputer object with), of which has a Description method that will now return the fully built description string we see in the console window!


In addition, because we are now programming to an interface rather than an implementation, we are free to add any number of selectable features and any number of different types of computers in the future without changing our existing code.

For example, say we want our users to be able to add a printer to the list of selectable features. All we have to do is create a new class, have it implement the ComputerDecorator interface (of which in turn derives from the Computer abstract class) and then call this line to wrap the Computer object with a printer

computer = new Printer(computer);


Then that’s it! You have successfully added a new feature! How painless was that ?!? This is possible due to polymorphism. Because all the features derive from the ComputerDecorator abstract class, of which in turn derives from the Computer abstract class, all the features (and all the different types of computers too) can be treated as objects of the same type. In this case, objects of the Computer type. This means that client code has absolutely no idea that the decorators (the features added to the computers) exist.

We can wrap as many decorators as we want around our computer object, but because they all ultimately derive from the Computer abstract class, they are all of type Computer effectively. Therefore, client code is always dealing with an object of type Computer, it doesn’t care (or know about) the wrappers as they implement the Computer abstract class (albeit indirectly through the ComputerDecorate abstract class), so they too are of type Computer.

This transparency is very important to the effectiveness of the Decorator Pattern. It allows us to very easily add new features and, indeed, very easily add new types of computers as they all derive from the same Computer abstract class.

You will find that polymorphism and the idea of coding to an interface (this refers to a polymorphic interface and could be an abstract class or a C# interface) rather than an implementation is very prevalent throughout design patterns and, good design practices in general.

So there you have it! My example may have been a basic example, but you should get the idea.

[

Useful Resources on the Decorator Pattern


http://sourcemaking....ecorator/c%2523

http://www.oodesign....or-pattern.html

Head First: Design Patterns (book)

Design Patterns Simply (book)


Summary

Decorator is designed to let you add responsibilities to objects without sub classing. It is a very good pattern to use in certain situations. It allows you to add these responsibilities at run-time (dynamically) using wrapping and composition, as opposed to at compile time (statically) using sub classing. Sub classing to achieve what this pattern achieves would be an absolute nightmare in many situations However, it has some minor pitfalls that you need to be aware of.

You also should practice knowing when the pattern should and shouldn’t be implemented. That is the really difficult part about Design Patterns.

The example above is obviously over simplified in the context of the real world, but it hopefully helps you understand the concept.

Thank you very much, and I hope you found this tutorial helpful in some way. I have attached the finished example of the decorator pattern below. I have included it all in one console application, but, in reality, you would probably compile the decorator class hierarchy into it’s own class library dll so it could be ‘consumed’ by any client that wishes to use it.

NOTE: that I have used the double data type to represent cost. Technically, you should use decimal for currency values, but, just for this example, I used the well known double type so as not add confusion.

Attached File(s)


This post has been edited by [email protected]: 24 February 2011 - 01:55 AM


Is This A Good Question/Topic? 2
  • +

Page 1 of 1