Decorator Design Pattern

“linked list of features”

The decorator design pattern allows us to add new behaviors to objects dynamically by placing them inside special wrapper objects, called decorators.

When to use Decorator Pattern

Add / remove behaviour at run-time with a polymorphic class.

Resource: https://refactoring.guru/design-patterns/decorator/cpp/example#lang-features

Learned in CS247 - Software Engineering Principles.

Pieces of the decorator pattern:

  • Abstract Component: Gives the interface for our classes
  • Concrete Component: Implements the “basic” version of the interface
  • Abstract Decorator: Organizes decorators and has-a decorator or the concrete component.
  • Concrete Decorators: Implement the interface, call operation() on the next object in this linked list.

Template code for a Pizza example (UML below)

class Pizza {
	public:
		virtual ~Pizza(){}
		virtual float price() const = 0;
		virtual string desc() const = 0;
};
 
class CrustAndSauce: public Pizza {
	public:
		float price() const override {return 5.99;};
		string desc() const override {return "pizza";};
};
 
class Decorator: public Pizza {
	protected:
		Pizza* next;
	public:
		Decorator(Pizza* next): next{next} {}
		~Decorator() {delete next;}
};
 
class Topping: public Decorator {
	string name;
	public:
		Topping(const string& s, Pizza* p): Decorator{p}, name{s} {}
		float price() const override {
			return 0.75 + next->price();
		}
		string desc() const override {
			return next->desc() + " with" + name;
		}
};
 
class StuffedCrust: public Decorator {
	public:
		StuffedCrust(Pizza* p): Decorator{p} {}
		float price() const override {
			return next->price() + 2.50;
		}
		string desc() const override {
			return next->desc() + " with stuffed crust";
		}
};
 
Pizza* p = new CrustAndSauce{};
p = new Topping{"cheese", p};
p = new Topping{"pepperoni", p};
p = new StuffedCrust{p};
cout << p->price() << " " << p->desc() << endl;
// Output: 9.99 pizza withcheese withpepperoni with stuffed crust
delete p;

Example:

#include <iostream>
#include <string>
 
class Text {
public:
    virtual std::string getContent() const = 0;
};
 
class PlainText : public Text {
    std::string content;
public:
    PlainText(const std::string& content) : content(content) {}
 
    std::string getContent() const override {
        return content;
    }
};
 
class TextDecorator : public Text {
    Text* wrappedText;
public:
    TextDecorator(Text* wrappedText) : wrappedText(wrappedText) {}
 
    std::string getContent() const override {
        return wrappedText->getContent();
    }
};
 
class BoldTextDecorator : public TextDecorator {
public:
    BoldTextDecorator(Text* wrappedText) : TextDecorator(wrappedText) {}
 
    std::string getContent() const override {
        return "<b>" + TextDecorator::getContent() + "</b>";
    }
};
 
class ItalicTextDecorator : public TextDecorator {
public:
    ItalicTextDecorator(Text* wrappedText) : TextDecorator(wrappedText) {}
 
    std::string getContent() const override {
        return "<i>" + TextDecorator::getContent() + "</i>";
    }
};
 
int main() {
    Text* text = new PlainText("Hello, Decorator Pattern!");
 
    // Wrap with decorators
    Text* boldText = new BoldTextDecorator(text);
    Text* italicBoldText = new ItalicTextDecorator(boldText);
 
    std::cout << "Original: " << text->getContent() << std::endl;
    std::cout << "Bold: " << boldText->getContent() << std::endl;
    std::cout << "Italic Bold: " << italicBoldText->getContent() << std::endl;
 
    delete text;
    delete boldText;
    delete italicBoldText;
 
    return 0;
}