4 colored paint brushes

What is the Decorator Pattern

The Decorator pattern is a structural design pattern used to extend the functionality of objects in a flexible and reusable way.

In C#, using the Decorator design pattern involves creating a set of decorator classes that are used to wrap concrete components. Here is a concrete example and some common use cases.

Let’s create a simple example involving a Notifier system where we can send notifications via different channels (Email, SMS, etc.).

Step 1: Create a Component Interface

This step is essential to define the contract that the decorators will need to implement.

1
2
3
4
public interface INotifier
{
    void Send(string message);
}

Step 2: Create a Concrete Component

1
2
3
4
5
6
7
public class EmailNotifier : INotifier
{
    public void Send(string message)
    {
        Console.WriteLine($"Sending Email: {message}");
    }
}

Step 3: Create Base Decorator Class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
public class NotifierDecorator : INotifier
{
    protected INotifier _notifier;

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

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

Step 4: Create Concrete Decorators

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class SMSNotifier : NotifierDecorator
{
    public SMSNotifier(INotifier notifier) : base(notifier)
    {
    }

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

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

    public override void Send(string message)
    {
        base.Send(message);
        Console.WriteLine($"And posting on Facebook: {message}");
    }
}

Step 5: Use Decorators

Let’s put the code to the test:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine($"## Use case: just Email");
        INotifier emailNotifier = new EmailNotifier();
        emailNotifier.Send("Hello World!");

        Console.WriteLine($"## Use case: Email+SMS");
        var smsNotifier = new SMSNotifier(emailNotifier);
        smsNotifier.Send("Hello World!");

        Console.WriteLine($"## Use case: Email+SMS+Facebook");
        var facebookNotifier = new FacebookNotifier(smsNotifier);
        facebookNotifier.Send("Hello World!");

        Console.WriteLine($"## Use case: Email+Facebook");
        var facebookNotifier2 = new FacebookNotifier(emailNotifier);
        facebookNotifier2.Send("Hello World!");
    }
}

What’s the Output?

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
## Use case: just Email
Sending Email: Hello World!
## Use case: Email+SMS
Sending Email: Hello World!
And sending SMS: Hello World!
## Use case: Email+SMS+Facebook
Sending Email: Hello World!
And sending SMS: Hello World!
And posting on Facebook: Hello World!
## Use case: Email+Facebook
Sending Email: Hello World!
And posting on Facebook: Hello World!

Step by Step Explanation

The first test is simple. Nothing special to explain.

The second, third and fourth tests show the Decorator pattern in action.

If you look at the third test, facebookNotifier wraps smsNotifier, itself wrapping emailNotifier.

emailNotifier is the base and when you call facebookNotifier.Send("Hello World!"), here’s what happens:

  • FacebookNotifier.Send is called and the first thing happening within the method is a call to base.Send(message) (which is smsNotifier.Send).

  • So then SMSNotifier.Send is called. Again the first thing happening within the method is a call to base.Send(message) (which is emailNotifier.Send).

  • So EmailNotifier.Send is called and it prints “Sending Email: Hello World!”.

  • Then control returns to SMSNotifier.Send, which prints “And sending SMS: Hello World!”.

  • And finally control returns to FacebookNotifier.Send, which prints “And posting on Facebook: Hello World!”.

Each decorator adds its own behavior after calling the wrapped object’s method.

1
2
3
4
5
6
FacebookNotifier.Send
    -> SMSNotifier.Send
        -> EmailNotifier.Send
            (prints "Sending Email: Hello World!")
        (prints "And sending SMS: Hello World!")
    (prints "And posting on Facebook: Hello World!")

Common Use Cases

When you want to:

  1. Extend Functionality: When you want to add responsibilities to individual objects, not to an entire class.
  2. Combine Behaviors: When you need to add a combination of behaviors at runtime. For example, combining various logging, authentication, or notification behaviors.
  3. Adhere to Single Responsibility Principle: By using decorators, you can divide a class’s functionality into separate classes with specific responsibilities.
  4. Build User Interface: Wrapping user interface elements to add responsibilities like borders, scrollbars, or decorations.
  5. Input/Output Streams: Adding responsibilities to streams (e.g., adding buffering, encryption, compression, etc.).

Sources

The resources below provide a comprehensive understanding of the Decorator pattern and its applications in software design.

  • Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (Gang of Four)
  • Head First Design Patterns by Eric Freeman, Elisabeth Robson
  • Microsoft Documentation on the Decorator Pattern

Follow me

Thanks for reading this article. Make sure to follow me on X, subscribe to my Substack publication and bookmark my blog to read more in the future.

Photo by Nataliya Vaitkevich

License GPLv3 | Terms
Built with Hugo
Theme Stack designed by Jimmy