Decorator design pattern

Decorator design pattern #

Name: Decorator

Alternative name: Wrapper

Type: Structural

Intent: #

Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.

When to use: #

The decorator pattern is particularly useful when we want to provide a myriad of customization options for a basic/base object because it allows us to add new functionalities to an object without altering its internal structure.

Real-life analogy: #

For example, when ordering a coffee, we might like that coffee with sugar, milk, or soy milk and we might add some cinnamon or whipped cream. Or, we might want some nice sprinkles on top. The sky is the limit here.

Another example is cake 🎂. We all love cakes,(I definitely do) and the variations are endless. Any cake can be customized in numerous ways to create a unique and memorable treat.

IT can start from a base cake and take different forms and shapes depending on who orders or eats it. Variations encompass diverse cake bases, tantalizing flavors, fillings, enticing fruit layers, rich icings, and eye-catching decorations🧁.

Imagine how such a scenario would look in terms of code.

Usually, when it comes to altering an object's structure or behavior, inheritance might be the first thing that comes to mind, but it might not be the best thing. In this scenario, subclassing wouldn’t be a great idea at all. Moreover, in time, we would have so many classes and spaghetti code that it would become impossible to manage.

By using inheritance, it would mean changing an object with another one, and it would only allow us to have one single base class. Inheritance is static and we need a way to alter the object’s structure, dynamically, at runtime.

One way to overcome this static issue is to use Aggregation or Composition. (remember- there is also a design principle that tells us to favor composition over inheritance.!!).

it is better to compose objects to achieve polymorphic behavior and code reuse rather than inheriting from a base class.

These two concepts sit behind many design patterns, and this happens here too. So, let’s see how to model such a thing in C#.

Structure: #

Implementation #

First, let’s have a look at the components needed:

  • ICake - the abstraction common
  • BaseCake class - that will implement the interface and act as our base object to which we will add enhancements to
  • BaseCakeDecorator - an abstract class that contains the object that we wrap as a field. The field’s type should be declared as the component interface so it can contain both concrete components and decorators. The base decorator delegates all operations to the wrapped object.
  • ChocolateFilling - is a concrete decorator (i.e. a concrete implementation of the BaseCakeDecorator) that we will use to wrap our BaseCake, to customize it as we want
  • Spinkles - it can be added as decoration too
public interface ICake
{
    string GetDescription();
    decimal GetCost();
}

 public class BasicCake : ICake
 {
     public string GetDescription()
     {
         return "Basic Cake";
     }

     public decimal GetCost()
     {
         return 10.0m;
     }
 }
/// Decorator abstract class
public abstract class BaseCakeDecorator : ICake
{
    protected ICake decoratedCake;

    public BaseCakeDecorator(ICake cake)
    {
        decoratedCake = cake;
    }

    public virtual string GetDescription()
    {
        return decoratedCake.GetDescription();
    }

    public virtual decimal GetCost()
    {
        return decoratedCake.GetCost();
    }
}

// Concrete decorator: ChocolateFilling

public class ChocolateFilling : BaseCakeDecorator
{
    public ChocolateFilling(ICake cake) : base(cake)
    {
    }

    public override string GetDescription()
    {
        return $"{base.GetDescription()} with Chocolate Filling";
    }

    public override decimal GetCost()
    {
        return base.GetCost() + 5.0m;
    }
}
// Concrete decorator: ChocolateFilling

 public class Sprinkles : BaseCakeDecorator
 {
     public Sprinkles(ICake cake) : base(cake)
     {
     }

     public override string GetDescription()
     {
         return $"{base.GetDescription()} with Sprinkles";
     }

     public override decimal GetCost()
     {
         return base.GetCost() + 2.0m;
     }
 }

Now we can create complex variations by using the concrete decorators that we have, starting with the base. Then, we can take the base and apply different decorators. The example below is exaggerated, but I hope it gives you an idea about how it can be used


ICake basic = new BasicCake();
ICake basicWithChoolateFilling = new ChocolateFilling(basicCake);
ICake basicWithChoolateFillingAndFruitFilling = new FruitFilling(basicWithChoolateFilling);
ICake basicWithChoolateFillingAndFruitFillingAndSprinkles = new Sprinkles(basicWithChoolateFillingAndFruitFilling);

Pros & Cons #

Pro #

  • We can give objects new responsibilities without making any code changes to the underlying classes.

Wearing clothes is an example of using decorators. If it is cold, we might layer ourselves with a few more clothes than in the summer. This is an example of using decorators at runtime. We are not born with clothes, and we don’t become different people just because we wear different clothes(if we were to use inheritance), we use clothes when we need them, as many as we need them.

  • Helps us 'achieve' Open/Closed principle The decorator has the same supertype as the object it decorates, we can pass around a decorated object in place of the original

  • encourages us to write code that adheres to the SOLID design principles.

  • gives us more flexibility than static inheritance

  • because decorators only rely on the interface of decorated objects (injected through the constructor of the decorator class), decorators can be unit tested independently.

CONS: #

  • All methods specified in the interface must be implemented in the concrete decorators, and if we don’t add additional functionality, we must use those as pass-through -** Lots of little objects**. A design that uses the Decorator design pattern often results in systems composed of lots of little objects that all look alike. The objects differ only in the way they are interconnected, not in their class or the value of their variables. Although these systems are easy to customize by those who understand them, they can be hard to learn and debug
  • Decorators can be tested in isolation of decorated objects but subclasses cannot be tested in isolation of their parent.

Summary #

The Decorator design pattern acts like a wrapper, giving us the ability to enhance base objects. This enhancement is dynamic, happening at run-time, without altering the base object’s structure. I can say that the decorator behaves very much like an extension method, that allows us to add extra functionality where we can’t or won’t change the base class. In essence, the decorator design pattern provides us a flexible mechanism to extend the functionality of existing objects, without changing their internal structure. It is like changing the case of your phone, not the internal structure of it