How to Implement the Decorator Pattern in C
This article provides a direct, practical guide to implementing the Decorator design pattern in C# using interface composition and wrapper classes. You will learn the structural components of the pattern, examine a clean and fully functional code example, and see how wrapping instances allows you to attach new responsibilities to objects dynamically at runtime without modifying existing code or relying on rigid class inheritance.
Understanding the Pattern Structure
The Decorator pattern attaches additional responsibilities to an object dynamically. It relies on object composition rather than inheritance to extend behavior. To implement it in C#, you need three key elements:
- Component Interface: Defines the shared contract for both the base component and the wrappers.
- Concrete Component: The core implementation that performs the primary work.
- Decorator Class (Wrapper): Implements the same component interface and maintains a reference to an instance of that interface, delegating calls to it while adding pre- or post-processing behavior.
Step 1: Define the Component Interface
Start by defining the interface that establishes the shared contract.
public interface INotifier
{
void Send(string message);
}Step 2: Create the Concrete Component
Implement the base class that delivers the primary functionality.
public class EmailNotifier : INotifier
{
public void Send(string message)
{
Console.WriteLine($"Sending Email: {message}");
}
}Step 3: Implement the Base Decorator
The base decorator implements INotifier and accepts an
INotifier instance through constructor injection. It
delegates default operations to the wrapped instance.
public abstract class NotifierDecorator : INotifier
{
protected readonly INotifier _innerNotifier;
protected NotifierDecorator(INotifier innerNotifier)
{
_innerNotifier = innerNotifier ?? throw new ArgumentNullException(nameof(innerNotifier));
}
public virtual void Send(string message)
{
_innerNotifier.Send(message);
}
}Step 4: Implement Concrete Wrapper Classes
Create concrete decorators that inherit from
NotifierDecorator to inject custom logic before or after
forwarding the call.
public class LoggingNotifierDecorator : NotifierDecorator
{
public LoggingNotifierDecorator(INotifier innerNotifier)
: base(innerNotifier) { }
public override void Send(string message)
{
Console.WriteLine($"[Log Entry - {DateTime.UtcNow:O}]: Initiating send.");
base.Send(message);
Console.WriteLine($"[Log Entry - {DateTime.UtcNow:O}]: Send completed.");
}
}
public class EncryptionNotifierDecorator : NotifierDecorator
{
public EncryptionNotifierDecorator(INotifier innerNotifier)
: base(innerNotifier) { }
public override void Send(string message)
{
string encryptedMessage = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes(message));
Console.WriteLine($"[Encryption]: Message encrypted to {encryptedMessage}");
base.Send(encryptedMessage);
}
}Step 5: Compose the Wrappers at Runtime
Because each decorator conforms to INotifier and accepts
an INotifier, you can nest wrappers in any combination or
order.
class Program
{
static void Main()
{
// Base component
INotifier notifier = new EmailNotifier();
// Wrap with encryption
notifier = new EncryptionNotifierDecorator(notifier);
// Wrap with logging
notifier = new LoggingNotifierDecorator(notifier);
// Execute the pipeline
notifier.Send("Order #10492 has been placed.");
}
}Output
[Log Entry - 2023-10-24T12:00:00.0000000Z]: Initiating send.
[Encryption]: Message encrypted to T3JkZXIgIzEwNDkyIGhhcyBiZWVuIHBsYWNlZC4=
Sending Email: T3JkZXIgIzEwNDkyIGhhcyBiZWVuIHBsYWNlZC4=
[Log Entry - 2023-10-24T12:00:00.0000000Z]: Send completed.
Why Use Interface Composition Over Inheritance?
- Adherence to the Open/Closed Principle: You add functionality like caching, retries, logging, or encryption without altering existing class implementations.
- Single Responsibility Principle: Distinct concerns reside in their own classes rather than polluting the core business component.
- Dynamic Combinations: Behaviors can be chained and
ordered at runtime instead of producing an exponential number of
subclass permutations (e.g.,
LoggingEmailNotifier,EncryptedLoggingEmailNotifier).