Friday, November 25, 2011

The Decorator Pattern

Subclassing is a powerful way of extending the behavior of a class while keeping what was already there intact. And is, of course, an integral part of object oriented programming. It does however have its drawbacks. I'll illustrate some with a simple example.

Let's say you're in a shop selling a dagger. Maybe there is a base price for the dagger but what you get for the dagger can rely on many factors. The type and quality of the dagger for example, and perhaps there's even an enchantment on it that increases it's value.
So, let's say a dagger class looks something like this:
public abstract class Dagger
{
    public abstract double GetValue();
}
Short, yes but this is all we need for our example.
We'll make a new class now, BronzeDagger, that will inherit from our Dagger class.
public class BronzeDagger : Dagger
{   
    public double GetValue()
    {
        //Return the dagger's base value.
        return 200d;
    }
}
Seems okay so far... we could continue, easily enough to make more classes for Iron, Silver, etc.
But what about that enchantment? If we were to continue in this fashion we'd be forced to make EnchantedBronzeDagger classes and EnchantedSilverDaggerClasses.

Perhaps though, one might suggest that we simply keep track of fields inside the base class like condition and an enchantment type of some kind. Let's see what that would look like.
public abstract class Dagger
{
    double _conditionFactor = .74d;
    double _enchantmentFactor = 2d;

    public abstract double GetValue();

    public double GetConditionFactor()
    {
        return _conditionFactor;
    }

    public double GetEnchantmentFactor()
    {
        return _enchantmentFactor;
    }
}

public class BronzeDagger : Dagger
{
    public double GetValue()
    {
        return 200d * GetCondition() * GetEnchantmentFactor();
    }
}

public class SilverDagger : Dagger
{
    public double GetValue()
    {
        return 500d * GetCondition() * GetEnchantmentValue();
    }
}

However, as more price altering factors are needed, the amount of code we have to write gets out of hand and we'd be forced to change the base class code each time.

The Decorator Pattern allows us to extend behavior dynamically without subclassing and without altering existing code.
The basic idea is to encapsulate the extended behavior into their own classes. We 'wrap' these extended behaviors, or decorators around the class we wish to add to.

Here's what a UML diagram for the Decorator pattern looks like:

The Component would be the object we wish to decorate. The Decorator class inherits from this base class and also contains a reference to one. In this diagram it's the abstract decorator that contains a Component reference which is fine, probably ideal, but for this quick example the Component reference is kept in the ConcreteDecorator classes. Finally there is a ConcreteComponent which inherits from the Component class as well.
That's the set-up, but how we use it will become clearer as we move along.

In our example we'd still have our base abstract Dagger (our Component) class:
public abstract class Dagger
{
    public abstract double GetValue();
}
And some subclasses:
public class BronzeDagger : Dagger
{
    public double GetValue()
    {
        return 200d;
    }
}

public class SilverDagger : Dagger
{
    public double GetValue()
    {
        return 200d;
    }
}

Now we'll make our abstract decorator class.
public abstract class DaggerDecorator : Dagger
{
    public abstract double GetValue();
}
It's important to note here while decorator class inherits from Dagger we're not using it in the normal sense. We're using it here for the type matching or mirroring. Let's make some concrete decorators.
public abstract class EnchantmentDecorator : DaggerDecorator
{
    //We have a reference to the object (or its outer most wrapper)
    //that we're decorating.
    Dagger _dagger;

    //The constructor gets passed a dagger which can 
    //be a decorator since the types match.
    public EnchantmentDecorator(Dagger dagger)
    {
        _dagger = dagger;
    }

    public double GetValue()
    {
        //This decorator multiplies the value by two.
        return 2 * _dagger.GetValue();
    }
}

public abstract class ConditionDecorator : DaggerDecorator
{
    //We have a reference to the object (or its outer most wrapper)
    //that we're decorating.
    Dagger _dagger;
    
    //The constructor gets passed a dagger which can 
    //be a decorator since the types match.
    public Decorator(Dagger dagger)
    {
        _dagger = dagger;
    }

    public double GetValue()
    {
        return .75d * _dagger.GetValue();
    }
}
Each decorator adds its own contribution to the value by delegating the method call, GetValue().
Let's see this in action and create a dagger, decorate it and find its value.

class Program
{
    static void Main(string[] args)
    {
        //Create a BronzeDagger...
        BronzeDagger _kitchenKnife = new BronzeDagger();

        //Wrap it in an EnchantMentDecorator...
        _kitchenKnife = new EnchantmentDecorator(_kitchenKnife);
        
        //Make a SilverDagger.
        SilverDagger _sting = new SilverDagger();
        //This one gets two decorators.
        _sting = new ConditionDecorator(_sting);
        _sting = new EnchantmentDecorator(_sting);
        
        double stingValue = _sting.GetValue();
        double knifeValue = _kitchenKnife.GetValue();

        Console.WriteLine(stingValue.ToString());
        Console.WriteLine(knifeValue.ToString());
    }
}
When we call _sting.GetValue(), it first calls the EnchantmentDecorator's GetValue() method, which then calls the ConditionDecorator's GetValue() which finally calls the original SilverDagger's GetValue() and each call contributes to the final value in some way.
In this example, only multiplication was used in calculating the final value, so the order in which I wrapped them and the order that each decorator delegated its behavior didn't matter. In more complicated examples these things have to be taken into account. We can add as many decorators to an object that we want, extending the class's functionality any time, and the original Dagger class and its subclasses are never altered in the process. Decorators can even add new methods.

Well, that's the Decorator Pattern in a nutshell! I hope you are enjoying this series and thanks for reading!

No comments :

Post a Comment