Menu Close

Object Oriented Design Pattern: Decorator

labrador pup

Explanation

The decorator design pattern is a structural pattern that is used to extend the functionality of an object of a class without actually extending it. Instead, you will add the class to a wrapper class that has extended functionality.

Use Cases

  • You want to add specific functionality to an object (at runtime).
  • You want to prevent to add this functionality by inheritance, to prevent possible overhead.

Pitfalls

  • Different “decorators” might be incompatible with each other.
  • If you create a lot of different decorator classes it might be somewhat hard to manage.

Example

Extending the functionality of a Dog

Imagine we have a Dog class that has an age attribute, an amount of stamina and that can run(), jump() and can just be().

Now suppose we want to learn our Dog class to bark. We could just extend the Dog class and add the functionality.

Somewhat later, a decision has been made that the Dog should be able the Shake a Paw as well. Or maybe your Dog is an avatar that is able to learn this trick.

Class diagram: Extending the Dog class

The class diagram above has ShakePawDog extend BarkableDog which extends our Dog base class. This is rather inflexible, because what if you want a Dog that can Shake a Paw but should not be able to Bark? And what if more and more functionality is added to the program to enhance the Dog?

Decorator pattern

Class Diagram for Dog Decorator

To decorate our Dog with Dog Tricks: RollOver, ShakePaw or Bark, we first need to choose a Dog to create, which can either be a Labrador or Shepherd in this example.

Code

First we create an IDog interface and an AbstractDog that implements that interface. The Labrador and Shepherd class are implementations of the AbstractDog.

namespace DesignPatterns.Decorator
{
    public interface IDog
    {
        public void Be();
        public void Run();
        public void Jump();
        public void DoTrick();
    }
    
    abstract public class AbstractDog : IDog
    {
        public int Age { get; set; }
        public float Stamina { get; set; }
        public abstract void Be();
        public abstract void DoTrick();
        public abstract void Jump();
        public abstract void Run();
    }

    public class Labrador : AbstractDog
    {
        override public void Be() { }
        override public void DoTrick() {
            Console.WriteLine("Labrador does trick");
        }
        override public void Jump() { }
        override public void Run() { }
    }

    public class Shepherd : AbstractDog
    {
        override public void Be() { }
        override public void DoTrick() {
            Console.WriteLine("Shepherd does trick");
        }
        override public void Jump() { }
        override public void Run() { }
    }
}
C#

Next we create a DogDecorator class which holds an IDog object. In our case, this is a Labrador or a Shepherd. The DogDecorator itself is abstract so we have to extend it to create decorators for our dogs. Each decorator will add new functionality to a dog. We can learn our dog new tricks this way. These decorators could be added in runtime as well.

namespace DesignPatterns.Decorator
{
    abstract public class DogDecorator : IDog
    {
        public required IDog DecoratedDog { get; set; }

        public void Be()
        {
            DecoratedDog.Be();
        }

        virtual public void DoTrick()
        {
            DecoratedDog.DoTrick();
        }

        public void Jump()
        {
            DecoratedDog.Jump();
        }

        public void Run()
        {
            DecoratedDog.Run();
        }
    }

    public class ShakePawDecorator: DogDecorator
    {
        override public void DoTrick()
        {
            base.DoTrick();
            ShakePaw();
        }

        public void ShakePaw()
        {
            Console.WriteLine("Shake paw");
        }
    }

    public class BarkDecorator : DogDecorator
    {
        override public void DoTrick()
        {
            base.DoTrick();
            Bark();
        }

        public void Bark()
        {
            Console.WriteLine("Bark");
        }
    }

    public class RollOverDecorator : DogDecorator
    {
        override public void DoTrick()
        {
            base.DoTrick();
            RollOver();
        }

        public void RollOver()
        {
            Console.WriteLine("RollOver");
        }
    }
}
C#

Our main program will create a new Labrador and will learn it new tricks one by one.

using DesignPatterns.Decorator;

class Program
{

    static void Main(string[] args)
    {
        // create a labrador
        Labrador labrador = new Labrador();

        // add labrador to the Bark decorator
        BarkDecorator barkDecorator = new BarkDecorator { DecoratedDog = labrador };
        barkDecorator.DoTrick(); // bark

        ShakePawDecorator shakePawDecorator = new ShakePawDecorator { DecoratedDog = labrador };
        shakePawDecorator.DoTrick(); // shake paw

        RollOverDecorator rollOverDecorator = new RollOverDecorator { DecoratedDog = labrador };
        rollOverDecorator.DoTrick(); // roll over
    }
}
C#
Output:

Labrador does trick
Bark
Labrador does trick
Shake paw
Labrador does trick
RollOver

Conclusion

The decorator design pattern can be used to add new behavior to classes at runtime or before. It is a powerful and flexible way to extend the functionality of objects without altering their structure. Although the trick functions (such as Bark) have been made public in this example, this does not have to be the case.

References

Freeman, E., Bates, B., & Sierra, K. (2004). Head first design patterns.

Gamma, E., Helm, R., Johnson, R., & Vlissides, J. (1994). Design Patterns: Elements of Reusable Object-Oriented Software.

Wikipedia contributors. (2024, 10 september). Software design pattern. Wikipedia. https://en.wikipedia.org/wiki/Software_design_pattern

Related Posts