Click here to Skip to main content
15,890,185 members
Articles / Programming Languages / C#

A Problem-Solution Approach to Design Patterns: Extension on Demand with Decorator Pattern

Rate me:
Please Sign up or sign in to vote.
4.38/5 (4 votes)
10 Jan 2016CPOL7 min read 22K   311   2  
This article discusses the Decorator Design Pattern. The approach taken in this series is how a pattern of problem can be identified, analysed and solved with a suitable design pattern.

Prologue

If we look around, we will find that there are a lot of recurring problems around us, and if we look deeper, there is a pattern of reasons involved with each and every problem. To overcome the difficulties caused by the problem, we usually create solutions around the symptoms even if it is wise to identify the pattern of the problem and create a solution around the pattern so that the problem does not even occur.

Recurring problems are common in the world of programming. The consequences are most likely purchasing expensive equipments, repeating the same code over and over, rewriting and modifying a whole lot of things from scratch and in the worst case, shutdown of the product line if the modification cost is more than the revenue it would earn.

But the good thing is, in time, the lazy and smart programmers came up with patterns of techniques and designs to solve these day to day problems commonly named as design patterns.

Scope

As the name suggests, the decorator pattern is associated with decorating something - objects to be exact. An object can be extended in multiple ways. One way of doing it is inheritance. But when it requires to add extra properties, responsibilities or functionalities to the individual instances of a specific class but without affecting the main class, it falls in the scope of the Decorator Pattern.

Problem

Inheritence is the basic way of extending the objects in OOP. The concept is simple - create a base class, and extend it as required in its sub-classes. But it makes the extensions tightly bound with the base class so that the extensions cannot be dynamically used by the client. 

To explain the problem, let us assume  you went to a car shop and ordered for a car with spoiler; in the programming term, CarWithSpoiler. Once it is built, you wanted it to be blue colored. "It is just a colour sprayed over the car" you thought. But surprisingly, the car builder scrapped the whole car and started building a new ColouredCarWithSpoiler with the colour "Blue". While you were trying to swallow the idea, another customer came and asked for a blue car. The builder replied, "Can't do it mate, we only have CarCarWithSpoiler and ColouredCarWithSpoiler but no ColouredCar."

With your ultimate talent and genius, you managed to figure out the code working behind the scenes.

Decorator_Problem

There is an interface ICar which includes the behaviors of a car. For this instance, let's assume there is only one behavior GetDescription().

C#
public interface ICar
{
    string GetDescription();
}

There are two classes implementing that interface, Car and CarWithSpoiler. "Does a car with a spoiler do anything more than just a car?" You asked by yourself. "Nope, it does not change the car's behavior. It is just a spoiler installed on the car." You found your answer too. CarWithSpoiler extends Car with an attribute, Spoiler.

C#
public class Car : ICar
{
    public Car()
    {
        Console.WriteLine("Creating Car");
    }

    public string GetDescription()
    {
        return "Car";
    }
}
C#
public class CarWithSpoiler : Car, ICar
{
    public string Spoiler { get; set; }

    public CarWithSpoiler()
    {
        Console.WriteLine("Creating CarWithSpoiler");
        Spoiler = "Spoiler";
    }

    public string GetDescription()
    {
        return base.GetDescription() + " " + Spoiler;
    }
}

Now the car builder said something about ColouredCarWithSpoiler which also should have the same behavior as the car with a spoiler, but extends another attribute Colour. The constructor must have expect the colour to be passed through so it knows which colour to apply.

C#
public class ColouredCarWithSpoiler : CarWithSpoiler, ICar
{
    public string Colour { get; set; }

    public ColouredCarWithSpoiler(string colour)
    {
        Console.WriteLine("Creating ColouredCarWithSpoiler");

        Colour = colour;
    }

    public string GetDescription()
    {
        return Colour + " " + base.GetDescription();
    }
}

When you simulated the scenario:

C#
class Program
{
    static void Main(string[] args)
    {
        Test(); ;

        Console.ReadKey();
    }

    private static void Test()
    {
        Console.WriteLine("Testing Your Car without Decorator Pattern");
        Console.WriteLine("------------------------------------------");

        ICar carWithSpoiler = new CarWithSpoiler();

        Console.WriteLine("Oh snap!!! You wanted a colour... Scrap it and create a new one...");

        ICar colouredCarWithSpoiler = new ColouredCarWithSpoiler("Blue");

        Console.WriteLine(colouredCarWithSpoiler.GetDescription());
    }
}

So, there is the problem. The builder could not serve the other customer because there is no class defined in the system extending the Car with Colour. And also, if we consider the scalability, this approach of extension will be very expensive in this scenario. Think if there is a new accessory, say, "Fog Lights", every combination of "Spoiler", "Colour" and "Fog Lights" is needed to be implemented to support all customers. And that is seven extended classes in total to our Car class, 4 new extended classes for 1 new extension item.

So, if the extensions are independent of each other, there are 2 problems with this inheritance approach of extending objects.

  1. The client has to be sure what it needs before instantiation. Extensions cannot be added dynamically, whenever needed.
  2. If any new extension is needed, a lot of change needs to be done in the system to support all possible combinations.

Solution: The Decorator Pattern

In a situation where an object needs to be extended dynamically and each extension is independent of each other, a more flexible way to get it done is to wrap the core object with the extension codes. The wrapper class is called a Decorator. In formal words, the Decorator Pattern allows us to attach additional responsibility to an object dynamically without affecting the implementation of the object.

In this approach, the extension class has a "has-a" relationship with the core object class along with having an "is-a" relationship. In other words, besides extending the core object class, the extension class contains an instance of it and wraps it with extended properties and behaviour. As the decorator extends from the main object class, after wrapping the extended code, it remains substitutable as the main object.

The following figure can give us a better view:

Decorator Pattern

AbstractObject

The abstraction of the ConcreteObject.

AbstractDecorator

The abstraction for the decorators, extended from AbstractObject. It abstracts the behaviours of the AbstractObjects that needs to be decorated and contains an instance of AbstractObject.

ConcreteObject

The implementation of the AbstractObject.

ConcreteDecorator1 and ConcreteDecorator2

Extended from AbstractDecorator, implementing the abstract behaviours with additional responsibilities.

When to Apply

Decorator pattern can be applied in one or both of the following scenarios:

  1. It requires to add additional responsibilities, which can be withdrawn when needed, to individual objects dynamically.
  2. And it requires to be done without affecting other objects.
  3. A large number of independent extensions are independent of each other and it needs an exponentially larger amount of subclassing to support all possible combinations of extensions.

Implementation

Let's implement the solution of our current problem with the decorator pattern.

Decorator_Solution

As we already had, there is an abstraction for the named ICar which is already implemented by Car.

C#
public interface ICar
{
    string GetDescription();
}
C#
public class Car : ICar
{
    public Car()
    {
        Console.WriteLine("Creating Car");
    }
    public string GetDescription()
    {
        return "Car";
    }
}

Now as per the decorator pattern, we need to create an abstraction for the decorators which implements ICar and contains an ICar. Let's name it CarDecoratorBase.

C#
public abstract class CarDecoratorBase : ICar
{
    protected ICar car;

    public abstract string GetDescription();
}

Once we have our abstraction for the decorators, let's move on to the concrete decorators and implement the abstract behaviour. We are not modifying the behaviour of the car that is passed through the constructor, but adding something extra after it.

C#
public class Spoiler : CarDecoratorBase
{
    public Spoiler(ICar car)
    {
        Console.WriteLine("Creating Spoiler");
        this.car = car;
    }

    public override string GetDescription()
    {
        return this.car.GetDescription() + " + Spoiler";
    }
}

Same thing is happening here, except we are adding the decoration first and then executing the car's behaviour.

C#
public class Colour : CarDecoratorBase
{
    string _colour;

    public Colour(ICar car, string colour)
    {
        Console.WriteLine("Applying Colour");
        this.car = car;
        _colour = colour;
    }

    public override string GetDescription()
    {
        return _colour + " " + this.car.GetDescription();
    }
}

Now, give it a test.

C#
class Program
{
    static void Main(string[] args)
    {
        Test();

        Console.ReadKey();
    }

    private static void Test()
    {
        Console.WriteLine("Testing Your Car with Decorator Pattern");
        Console.WriteLine("---------------------------------------");

        ICar car = new Car();

        car = new Spoiler(car);

        Console.WriteLine("Oh snap!!! You wanted a colour... No worries :)... Decorate it with colour");

        car = new Colour(car, "Blue");

        Console.WriteLine(car.GetDescription());
        Console.WriteLine("\n----------------------------------");

        Console.WriteLine("\nNew Customer wants a Green Car.\n");

        Decorator.Solution.ICar car2 = new Decorator.Solution.Car();

        car2 = new Colour(car2, "Green");

        Console.WriteLine(car2.GetDescription());
    }
}

Hmm, seems we are able to deliver any request possible with the resources we have.

Pros and Cons

There are pros and cons of everything. If implemented right, it comes with two major benefits:

  1. It allows more flexibility for extending object by adding responsibilities than the inheritence approach.
  2. Every decorator is self-descriptive. So, we don't need classes with all the combinations of responsibilities.

Now, for cons, from a real world perspective, it creates little ambiguities as decorators are extensions of the abstract object, while they often are not. In our example, spoilers are not really cars although they implemented ICar. It also make it hard to read and understand the code as it becomes difficult to track the chain of decoration if there ara a fairly large number of decorators involved.

Yet, being lightweight and easy to implement, decorator becomes very useful and handy in needs. 

Practical Example

Assume you have to create a button which will cater for all sizes of devices. For small devices, it will show an icon, in medium devices, it will show a text and in large devices, it will show the icon and the text together. The decorator pattern is a perfect fit for this situation where you can decorate a button with texts and icons.

Conclusion

Despite the liabilities it brings, the decorator pattern is very easy to understand and implement. The decorators come naturally by necessity. Also, decorators are generally very small, easy to maintain and more over, they are unit testable. When considering about the decorator pattern, you should also consider about other design patterns like bridge pattern, adapter pattern and composite pattern for a substitution or using in combination.

The source code is also available at GitHub.

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)
Australia Australia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --