Design Patterns — A quick guide to Decorator pattern.

Andreas Poyias
6 min readFeb 4, 2019

--

The Decorator pattern is a structural pattern that lets you attach additional functionalities to an object dynamically. In other words, the client has the freedom to create an object and then extend it by adding a variety of “features” to it. A good analogy to simplify this pattern is: “Wrapping a gift, putting it in a box, and wrapping the box”.

The Decorator pattern is classified among structural design patterns which are all about Class and Object composition. Structural class-creation patterns use inheritance to compose interfaces. Structural object-patterns define ways to compose objects to obtain new functionality. [by Design Patterns explained simply]

A backpack in the perfect example of a Decorator pattern:

  • Nowadays, the backpacks can have a variety of features. The features can range from something as simple as a laptop slot, side pockets, or even a power-bank for USB charging.
  • The main target of the Decorator pattern is to allow the client to add whatever features are required, dynamically, safely and in the easiest possible way. It should be as straight forward and tidy as fitting back the backpack in the picture.
  • The snippet of code below is purposely written as it converges so nicely with the quote we mentioned above: “Wrapping a gift, putting it in a box, and wrapping the box”. We assemble a backpack to which we add a USB charger to which we add a laptop slot to which we add
int main // the client 
{
IBackpack *bp = new LaptopSlot(new UsbCharge(...))));
return 1;
}

Step 1 — Keywords

Defining keywords is the secret recipe in this series of quick-guides. This method helped me truly understand the design patterns, hardcode them in my mind and comprehend the differences among other design patterns.

  • Flexibility: We want to give to the client the power and flexibility to dynamically add any feature to a component/object, that could be considered valuable.
  • Extend functionality: The title may be a little bit misleading, this pattern is not only about “decorating” a certain object, but it is mainly about extending its functionality.

Step 2 — Diagrams by example

Let’s start explaining the diagram from bottom to top. The target is to “assemble” a backpack and add several functionalities to it.

  • Concrete Decorators: We have three examples of these classes which are responsible to extend an extra functionality each: (1) LaptopSlot, (2) USBCharge, (3) WaterBottle. They hold the implementation of the functionality and they can “assemble” a backpack accordingly. Notice that they have a constructor that receives a decorator as a parameter.
  • Decorator: This class derives the above classes and inherits from the component which is IBackpack. The decorator also has an instance of IBackpack. After IBackpackis instantiated, it is being used inside the assemble()method.
  • Concrete Component: This class is the most important piece of the puzzle as it is the key to linking everything together. It is the component (backpack) which is in the plainest form it could get. For example, a plain backpack consists only of the “shoulder straps and main compartment”. The plain backpack performs as a start of a chain. It is passed to the decorator constructor and itself is passed to the constructor of a concrete decorator (to add specific functionalities i.e. a laptop slot) which can be passed to another constructor of a concrete decorator and another and so on.
  • Component: It is an abstract view of the object we want to decorate. We can have different concrete components inheriting from it. In this example we could be separating the “PlainBackpack” as “OfficeBackpack”, “CampingBackpack”, “HikingBackpack” but we choose to keep it simple.

Step 23— Code by example

I would suggest to copy the code class by class from my git repository “Andreas Poyias or the snippets below (in the order provided) and paste it in any of the available online C++ editors like c++shell, jdoodle , onlineGDBand run it to observe the output. Then read the comments or description below. Take time reading it thoroughly (that means one minute, not less and not more).

IBackpack:
The following code snippet is a simple inheritance, IBackpackis inherited by PlainBackpackclass. All derived classes must implement assemble(), since it is a pure virtual function = 0;. A plain backpack only has shoulder straps and the main compartment.

#include <iostream>
using namespace std;

class
IBackpack
{
public:
virtual void
assemble() = 0;
virtual ~IBackpack() {}
};

class PlainBackpack: public IBackpack
{
public:
virtual void
assemble(){cout<<"\n ShoulderStraps and mainCompartment";}
};

BackpackDecorator:
The above snippet is responsible for the construction of a plain backpack. Now, let’s decorate it using the BackpackDecorator. The decorator inherits from theIBackpack which means it must implement theassemble() method. It also contains an IBackpackobject which is used to delegate the implementation of the assemble()method depending on the type of m_decorator.

class BackpackDecorator: public IBackpack
{
public:
BackpackDecorator(IBackpack* decorator):m_Decorator(decorator) {}

virtual void assemble()
{
m_Decorator->assemble();
}
private:
IBackpack* m_Decorator;
};

Concrete decorators:
The snippet below shows three different decorators that are derived from theBackpackDecorator class which itself inherits fromIBackpack. In this example, they are all identical apart from the implementation ofassemble(). The first adds a laptopSlot, the second adds a UBCharge and the third adds a waterBottle.

class WithLaptopSlot : public BackpackDecorator
{
public:
WithLaptopSlot(IBackpack* dcrator):BackpackDecorator(dcrator){}
virtual void assemble()
{
BackpackDecorator::assemble();
cout << " + LaptopSlot";
}
};

class WithUSBCharge : public BackpackDecorator
{
public:
WithUSBCharge(IBackpack* dcrator):BackpackDecorator(dcrator){}
virtual void assemble()
{
BackpackDecorator::assemble();
cout << " + USBCharge";
}
};

class WithWaterBottle : public BackpackDecorator
{
public:
WithWaterBottle(IBackpack* dcrator):BackpackDecorator(dcrator){}
virtual void assemble()
{
BackpackDecorator::assemble();
cout << " + WaterBottle";
}
};

Main (Client):
The main method operates as the client(the same as the previous guides). We are finally able to put all the pieces of the puzzle together. But before doing so, let’s remember what was mentioned in the first paragraph of the blog “Wrapping a gift, putting it in a box, and wrapping the box”. To understand this we must read the construction of the backpack (in the snippet below) in reverse order:

  1. Create a PlainBackpack.
  2. Pass it to the BackpackDecorator.
  3. Which passes it along to be decorated with a laptop slot.
  4. In turn, it is passed to be decorated with a USB charge.
  5. Finally, the “box” is “wrapped” with a water bottle.

It is also important to observe the print order as assemble()is being called. The order is related to how the constructors were initialized. It again starts from the plain backpack and is decorated all the way with to reach waterbottle.

int main()
{
IBackpack* pBackpack =
new WithWaterBottle( //5
new WithUSBCharge( //4
new WithLaptopSlot( //3
new BackpackDecorator( //2
new PlainBackpack()))));//1

pBackpack->assemble();
delete pBackpack;

return 0;
}
// Output
// ShoulderStraps and mainCompartment + LaptopSlot + USBCharge
// + WaterBottle

The simplicity offered to the client is one of the biggest benefits.

  • The client has the power to dynamically assemble a backpack with any features available.
  • It is simple, easy and safe.
  • The client does not need to mingle with the code.
  • Adding an extra “derived” decorator is simple and independent from other derived decorators.

Don’t forget to like/clap my blog-post and follow my account. This is to give me the satisfaction that I helped some fellow developers and push me to keep on writing. If there is a specific design pattern that you would like to learn about then let me know in the comments below so I can provide it for you in the next few weeks.

Other quick-guides on design patterns:

  1. Design Patterns — A quick guide to Abstract Factory.
  2. Design Patterns — A quick guide to Bridge Pattern.
  3. Design Patterns — A quick guide to Builder Pattern.
  4. Design Patterns — A quick guide to Decorator Pattern.
  5. Design Patterns — A quick guide to Facade Pattern.
  6. Design Patterns — A quick guide to Observer Pattern.
  7. Design Patterns — A quick guide to Singleton Pattern.

--

--

Andreas Poyias

PhD in the development of data mining algorithms. I am a proud nerd that loves technology!