<aside> 📚 📁 Category: Structural Pattern

</aside>

Overview

Decorator is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors. It allows behavior to be added to individual objects, dynamically, without affecting the behavior of other objects from the same class.

<aside> â„šī¸ When to use: When you need to add responsibilities to objects dynamically and transparently without affecting other objects. When extension by subclassing is impractical or when you want to add functionality that can be withdrawn.

</aside>

Problem

You're working on a notification library. Initially, it only sends emails. Later, you need to add SMS, Slack, and Facebook notifications. You could create subclasses for each combination (EmailNotification, SMSNotification, EmailAndSMSNotification, etc.), but this leads to combinatorial explosion of classes.

Solution

Instead of inheritance, use "wrappers" (decorators). A wrapper is an object that can be linked with a target object. The wrapper delegates the request to the target and may perform something either before or after.

Implementation in C#

// Component interface
public interface INotifier
{
    void Send(string message);
}

// Concrete Component
public class EmailNotifier : INotifier
{
    public void Send(string message)
    {
        Console.WriteLine($"Sending email: {message}");
    }
}

// Base Decorator
public abstract class NotifierDecorator : INotifier
{
    protected INotifier _notifier;

    public NotifierDecorator(INotifier notifier)
    {
        _notifier = notifier;
    }

    public virtual void Send(string message)
    {
        _notifier.Send(message);
    }
}

// Concrete Decorators
public class SMSDecorator : NotifierDecorator
{
    public SMSDecorator(INotifier notifier) : base(notifier) { }

    public override void Send(string message)
    {
        base.Send(message);
        Console.WriteLine($"Sending SMS: {message}");
    }
}

public class SlackDecorator : NotifierDecorator
{
    public SlackDecorator(INotifier notifier) : base(notifier) { }

    public override void Send(string message)
    {
        base.Send(message);
        Console.WriteLine($"Sending Slack message: {message}");
    }
}

public class FacebookDecorator : NotifierDecorator
{
    public FacebookDecorator(INotifier notifier) : base(notifier) { }

    public override void Send(string message)
    {
        base.Send(message);
        Console.WriteLine($"Posting to Facebook: {message}");
    }
}
// Simple email notification
INotifier notifier = new EmailNotifier();
notifier.Send("Hello!");

// Email + SMS
INotifier emailAndSMS = new SMSDecorator(new EmailNotifier());
emailAndSMS.Send("Important update");

// Email + SMS + Slack + Facebook
INotifier allChannels = new FacebookDecorator(
    new SlackDecorator(
        new SMSDecorator(
            new EmailNotifier()
        )
    )
);
allChannels.Send("Critical alert!");

Implementation in Python

from abc import ABC, abstractmethod

# Component
class Notifier(ABC):
    @abstractmethod
    def send(self, message: str):
        pass

class EmailNotifier(Notifier):
    def send(self, message: str):
        print(f"Sending email: {message}")

# Base Decorator
class NotifierDecorator(Notifier):
    def __init__(self, notifier: Notifier):
        self._notifier = notifier
    
    def send(self, message: str):
        self._notifier.send(message)

# Concrete Decorators
class SMSDecorator(NotifierDecorator):
    def send(self, message: str):
        super().send(message)
        print(f"Sending SMS: {message}")

class SlackDecorator(NotifierDecorator):
    def send(self, message: str):
        super().send(message)
        print(f"Sending Slack: {message}")

# Usage
notifier = EmailNotifier()
notifier = SMSDecorator(notifier)
notifier = SlackDecorator(notifier)
notifier.send("Hello!")

# Output:
# Sending email: Hello!
# Sending SMS: Hello!
# Sending Slack: Hello!

Real-World Example: Coffee Shop

public interface ICoffee
{
    string GetDescription();
    double GetCost();
}

public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Simple coffee";
    public double GetCost() => 2.0;
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    
    public CoffeeDecorator(ICoffee coffee)
    {
        _coffee = coffee;
    }
    
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual double GetCost() => _coffee.GetCost();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", milk";
    public override double GetCost() => _coffee.GetCost() + 0.5;
}

public class SugarDecorator : CoffeeDecorator
{
    public SugarDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", sugar";
    public override double GetCost() => _coffee.GetCost() + 0.2;
}

public class WhippedCreamDecorator : CoffeeDecorator
{
    public WhippedCreamDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", whipped cream";
    public override double GetCost() => _coffee.GetCost() + 0.7;
}

// Usage
ICoffee coffee = new SimpleCoffee();
Console.WriteLine($"{coffee.GetDescription()} - ${coffee.GetCost()}");

coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
coffee = new WhippedCreamDecorator(coffee);

Console.WriteLine($"{coffee.GetDescription()} - ${coffee.GetCost()}");
// Output: Simple coffee, milk, sugar, whipped cream - $3.4

Pros and Cons