Table of Contents
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.
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
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