How to Use the Decorator Pattern in Csharp

The Decorator pattern is a structural design pattern that allows functionality to be added to an object dynamically. If you've ever worked with C#, understanding this pattern can save you countless hours and improve your code's flexibility. But how does it work, and why is it so effective? Let’s get into it.

What Is the Decorator Pattern?

At its core, the Decorator pattern involves wrapping an object to dynamically add behavior without altering the original object. Think of it like layering clothing—you can add layers (decorators) for extra functionality without changing the base garment (object).

This is especially useful in scenarios where subclassing isn’t practical due to too many potential combinations of behaviors.

Key Characteristics:

  • Extensibility: Add functionality without touching the original code.
  • Flexibility: Works at runtime, so you can change or add behavior on the fly.
  • Composition Over Inheritance: Avoids deep inheritance hierarchies.

When to Use the Decorator Pattern

Here are some examples where using this pattern makes sense:

  • You need to expand an object’s capabilities without modifying its source code.
  • You want to support open/closed principle by making classes open to extension but closed to modification.
  • Your program has a need for combining different features in various configurations.

Building the Decorator Pattern in C#

Let’s move into the implementation. Below, we’ll create a series of examples step-by-step to help you grasp how the Decorator pattern is implemented in C#.

1. Create a Base Interface

The base interface defines the functionality that all concrete components and decorators will share.

public interface INotification
{
    void Send(string message);
}

Here, the INotification interface ensures that all decorators and components share a common structure.

2. Build a Concrete Component

This is the default implementation for the interface.

public class EmailNotification : INotification
{
    public void Send(string message)
    {
        Console.WriteLine($"Email: {message}");
    }
}

The EmailNotification class implements INotification, and its Send method simply outputs the message.

3. Design an Abstract Decorator

The abstract decorator will wrap the concrete component while maintaining the same interface.

public abstract class NotificationDecorator : INotification
{
    protected INotification _notification;

    protected NotificationDecorator(INotification notification)
    {
        _notification = notification;
    }

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

Notice how NotificationDecorator delegates its Send method to the wrapped _notification.

4. Develop Concrete Decorators

These add specific behaviors by enhancing the Send method.

SMS Decorator:

public class SMSNotification : NotificationDecorator
{
    public SMSNotification(INotification notification) : base(notification) { }

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

Slack Decorator:

public class SlackNotification : NotificationDecorator
{
    public SlackNotification(INotification notification) : base(notification) { }

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

Each decorator overrides Send but also calls base.Send, ensuring the wrapped object’s Send method gets called.

5. Putting It All Together

Here’s how you can use the decorators in your program.

class Program
{
    static void Main()
    {
        INotification notification = new EmailNotification();

        // Wrap the notification with additional features
        notification = new SMSNotification(notification);
        notification = new SlackNotification(notification);

        notification.Send("Hello, world!");
    }
}

Output:

Email: Hello, world!
SMS: Hello, world!
Slack: Hello, world!

In this example, we’ve wrapped an email notification with both SMS and Slack decorators. The message gets sent through all channels.

Why You Should Use the Decorator Pattern

Here’s why you may find this pattern useful:

  • Reusability: Reuse decorators across various components.
  • No Code Duplication: Avoid rewriting logic for each subclass.
  • Dynamic Behavior: Apply changes at runtime, making your application adaptable.

For more insights into how C# supports such design patterns, check out Understanding C# Access Modifiers.

Conclusion

The Decorator pattern empowers you to enhance objects with new behavior while adhering to good software design principles like composition and open/closed. By understanding this pattern, you’re setting yourself up for cleaner, more adaptable code.

Want to explore more on C# core concepts? Read C# Variables: A Comprehensive Guide for tips on structuring your variables efficiently.

Now it’s your turn to experiment. Start building decorators in your own projects and experience the power of this design pattern firsthand!

Previous Post Next Post

Welcome, New Friend!

We're excited to have you here for the first time!

Enjoy your colorful journey with us!

Welcome Back!

Great to see you Again

If you like the content share to help someone

Thanks

Contact Form