I wanted to start the series out with the decorator pattern. It’s straightforward and helps maintain SOLID principles by allowing us to extend classes instead of modifying them.

Definition

The Decorator pattern allows behaviour to be added to an individual object, either statically or dynamically, without affecting the behaviour of other objects from the same class.

It is used to:

  • Enhance the functionality of an object at run-time by wrapping the object with additional behaviour.
  • Add behaviour to an object without having to create a new derived class.
  • Modify the behaviour of an object dynamically, while keeping the code maintainable and scalable.

Let’s see it in action

We start with our usual Animal base class

abstract class Animal
{
    public abstract string Description { get; }
    public abstract double Cost { get; }
}

Let’s define our concrete implementations of Animal, namely Lion, and Elephant.

class Lion : Animal
{
    public override string Description => "Lion";
    public override double Cost => 500;
}

class Elephant : Animal
{
    public override string Description => "Elephant";
    public override double Cost => 1000;
}

So far it’s all very familiar. But let’s say we want to be able to adjust the description but not adjust the base class. We’ll define a Decorator class first.

abstract class AnimalDecorator : Animal
{
    protected Animal animal;

    public AnimalDecorator(Animal animal)
    {
        this.animal = animal;
    }

    public override string Description => animal.Description;
    public override double Cost => animal.Cost;
}

Now we have a decorator class that protects our original object but allows overridable implementations.

class ByTruck : AnimalDecorator
{
    public WithTrainer(Animal animal) : base(animal) { }

    public override string Description => animal.Description + " transported by truck";
    public override double Cost => animal.Cost + 100;
}

class ByTrain: AnimalDecorator
{
    public WithShow(Animal animal) : base(animal) { }

    public override string Description => animal.Description + " transported by train";
    public override double Cost => animal.Cost + 200;
}

Now we have classes with which we can define how to transport animals without interfering with the original object. We can still call ByTruck.Animal.Cost to get the original cost. Extended without modification.

This is a very simple use case for the pattern and in all honesty, this could have been done in many different ways. As is the case with design patterns, there is quite a bit of thought required and a particular need required to implement them. Trying to force a pattern into a design is never a good idea.