Monday, July 13, 2026

Decorator Design Pattern

 Mastering Design Patterns in C# and ASP.NET Core

Part 3.4 – Decorator Design Pattern

Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Structural Design Pattern
Difficulty: ⭐⭐⭐⭐☆ (Intermediate)
Prerequisites: OOP Concepts, Interfaces, Composition, Dependency Injection, SOLID Principles


Table of Contents

  1. Introduction

  2. What is the Decorator Design Pattern?

  3. Why Do We Need the Decorator Pattern?

  4. The Problem with Inheritance

  5. Real-World Analogy

  6. Decorator Pattern Structure

  7. UML Class Diagram

  8. Components of the Decorator Pattern

  9. Complete C# Console Application

  10. Fluent Decorators

  11. ASP.NET Core Implementation

  12. Real-World Examples

  13. Advantages

  14. Disadvantages

  15. Best Practices

  16. Common Mistakes

  17. Decorator vs Adapter vs Proxy

  18. Middleware in ASP.NET Core (Decorator in Action)

  19. Interview Questions

  20. Summary


Introduction

One of the biggest challenges in software development is adding new functionality to existing classes without modifying their source code.

Consider an application that sends notifications.

Initially, it only supports:

  • Email

Later, business requirements change:

  • Email + Logging

  • Email + Encryption

  • Email + Validation

  • Email + Retry

  • Email + Audit

  • Email + Compression

Using inheritance, you might create classes like:

EmailNotification

LoggedEmailNotification

EncryptedEmailNotification

EncryptedLoggedEmailNotification

ValidatedEncryptedLoggedEmailNotification

RetryEncryptedLoggedEmailNotification

As features grow, the number of subclasses increases dramatically.

This is known as class explosion.

The Decorator Pattern solves this problem elegantly by allowing behaviors to be added dynamically at runtime.


What is the Decorator Design Pattern?

Definition

The Decorator Pattern is a Structural Design Pattern that allows behavior to be added to an object dynamically without modifying its existing code.

Instead of changing the original object, the object is wrapped inside one or more decorator objects, each adding additional behavior.

Think of it like wrapping a gift:

  • The gift remains the same.

  • Each wrapper adds something extra.


Why Do We Need the Decorator Pattern?

Suppose your application sends notifications.

Basic implementation:

Send Email

New requirements:

Validate

↓

Encrypt

↓

Log

↓

Retry

↓

Send Email

Without decorators, every combination would require a new subclass.

With decorators:

RetryDecorator
      ↓
LoggingDecorator
      ↓
EncryptionDecorator
      ↓
ValidationDecorator
      ↓
EmailNotification

Each decorator performs one responsibility.


The Problem with Inheritance

Imagine a coffee ordering system.

Base Coffee

Need:

  • Milk

  • Sugar

  • Chocolate

  • Cream

  • Caramel

Inheritance approach:

Coffee

MilkCoffee

SugarCoffee

MilkSugarCoffee

MilkChocolateCoffee

MilkChocolateSugarCoffee

MilkChocolateSugarCreamCoffee

The number of classes grows exponentially.

Decorators solve this by allowing ingredients to be added dynamically.


Real-World Analogy

Imagine buying a pizza.

Base Pizza

Optional Toppings

  • Cheese

  • Mushroom

  • Olive

  • Paneer

  • Corn

Each topping wraps the existing pizza.

The pizza itself never changes.

Exactly how decorators work.


Decorator Pattern Structure

The Decorator Pattern consists of:

  • Component

  • Concrete Component

  • Base Decorator

  • Concrete Decorators

  • Client

Every decorator implements the same interface as the object it decorates.


UML Class Diagram

                 +----------------------+
                 |     INotifier        |
                 +----------------------+
                 | + Send()             |
                 +----------^-----------+
                            |
          +-----------------+-----------------+
          |                                   |
+----------------------+        +---------------------------+
| EmailNotifier        |        | NotificationDecorator     |
+----------------------+        +---------------------------+
| Send()               |        | - notifier                |
+----------------------+        | + Send()                  |
                                +------------^--------------+
                                             |
              +------------------------------+-----------------------------+
              |                              |                             |
+--------------------------+    +------------------------+    +-----------------------+
| LoggingDecorator         |    | EncryptionDecorator    |    | RetryDecorator        |
+--------------------------+    +------------------------+    +-----------------------+
| Send()                   |    | Send()                 |    | Send()                |
+--------------------------+    +------------------------+    +-----------------------+

Components of the Decorator Pattern

Component

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 NotificationDecorator : INotifier
{
    protected readonly INotifier notifier;

    protected NotificationDecorator(INotifier notifier)
    {
        this.notifier = notifier;
    }

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

Logging Decorator

public class LoggingDecorator : NotificationDecorator
{
    public LoggingDecorator(INotifier notifier)
        : base(notifier)
    {
    }

    public override void Send(string message)
    {
        Console.WriteLine("Logging Notification");

        base.Send(message);
    }
}

Encryption Decorator

public class EncryptionDecorator : NotificationDecorator
{
    public EncryptionDecorator(INotifier notifier)
        : base(notifier)
    {
    }

    public override void Send(string message)
    {
        Console.WriteLine("Encrypting Message");

        base.Send(message);
    }
}

Complete C# Console Application

class Program
{
    static void Main()
    {
        INotifier notifier =
            new LoggingDecorator(
                new EncryptionDecorator(
                    new EmailNotifier()));

        notifier.Send("Welcome");
    }
}

Output

Logging Notification

Encrypting Message

Sending Email: Welcome

Each decorator adds functionality before passing control to the next object.


Fluent Decorators

Decorators can be chained dynamically.

INotifier notifier =
    new RetryDecorator(
        new LoggingDecorator(
            new EncryptionDecorator(
                new EmailNotifier())));

You can easily add or remove decorators without changing the base class.


ASP.NET Core Implementation

One of the best examples of the Decorator Pattern is ASP.NET Core Middleware.

Each middleware:

  • Receives the request

  • Performs additional work

  • Calls the next middleware

Pipeline:

Authentication

↓

Authorization

↓

Logging

↓

Exception Handling

↓

Controller

Each middleware decorates the next one.


Example Service

public interface IReportService
{
    void Generate();
}

Base Service

public class ReportService : IReportService
{
    public void Generate()
    {
        Console.WriteLine("Generating Report");
    }
}

Logging Decorator

public class LoggingReportService : IReportService
{
    private readonly IReportService report;

    public LoggingReportService(IReportService report)
    {
        this.report = report;
    }

    public void Generate()
    {
        Console.WriteLine("Log Started");

        report.Generate();

        Console.WriteLine("Log Finished");
    }
}

Dependency Injection

builder.Services.AddScoped<ReportService>();

builder.Services.AddScoped<IReportService>(provider =>
{
    return new LoggingReportService(
        provider.GetRequiredService<ReportService>());
});

Real-World Examples

The Decorator Pattern is commonly used when behavior needs to be added dynamically.

Coffee Shop

Coffee

Milk

Sugar

Chocolate


ASP.NET Core Middleware

Request

↓

Authentication

↓

Authorization

↓

Logging

↓

Controller

Stream Classes in .NET

Examples:

  • FileStream

  • BufferedStream

  • CryptoStream

  • GZipStream

Each stream decorates another stream.


Logging

Business Service

Logging Decorator

Audit Decorator

Caching Decorator


Payment Processing

Payment

Validation

Fraud Detection

Logging

Execution


Advantages

  • Follows the Open/Closed Principle.

  • Adds functionality dynamically.

  • Eliminates subclass explosion.

  • Promotes composition over inheritance.

  • Behaviors can be combined in different ways.

  • Each decorator has a single responsibility.

  • Highly reusable.


Disadvantages

  • Many small classes may be created.

  • Debugging long decorator chains can be difficult.

  • Order of decorators can affect behavior.

  • Configuration may become complex in very large systems.


Best Practices

  • Keep decorators focused on a single responsibility.

  • Use Dependency Injection for decorator registration.

  • Avoid adding unrelated business logic.

  • Document the order of decorators when it matters.

  • Prefer composition over inheritance.


Common Mistakes

Confusing Decorator with Inheritance

Decorators wrap objects at runtime.

Inheritance extends classes at compile time.


Large Decorators

Each decorator should perform one task only.

Examples:

  • Logging

  • Validation

  • Encryption

  • Retry


Ignoring Order

The following are not equivalent:

Logging

↓

Encryption

↓

Email

vs.

Encryption

↓

Logging

↓

Email

Execution order changes the result.


Using Decorators for Object Creation

Decorators enhance behavior.

Factories create objects.

Builders construct complex objects.


Decorator vs Adapter vs Proxy

FeatureDecoratorAdapterProxy
PurposeAdd behaviorConvert interfacesControl access
Changes Interface❌ No✅ Yes❌ No
Adds Features✅ Yes❌ NoSometimes
Wraps Object✅ Yes✅ Yes✅ Yes
Primary GoalExtend functionalityCompatibilityAccess control

Middleware in ASP.NET Core (Decorator in Action)

The ASP.NET Core request pipeline is a classic implementation of the Decorator Pattern.

HTTP Request

↓

Exception Middleware

↓

Authentication Middleware

↓

Authorization Middleware

↓

Logging Middleware

↓

MVC Controller

↓

HTTP Response

Each middleware:

  • Receives the request

  • Adds behavior

  • Calls the next middleware

  • Receives the response

  • Performs additional work

This layered architecture is one of the reasons ASP.NET Core is highly modular and extensible.


Interview Questions

1. What is the Decorator Design Pattern?

A structural design pattern that dynamically adds responsibilities to an object by wrapping it inside decorator objects.


2. What problem does the Decorator Pattern solve?

It avoids class explosion caused by inheritance when adding multiple optional behaviors.


3. What is the difference between Decorator and Inheritance?

  • Inheritance extends behavior at compile time.

  • Decorator extends behavior dynamically at runtime through composition.


4. Which SOLID principles does the Decorator Pattern support?

  • Open/Closed Principle (OCP) – New behaviors can be added without modifying existing classes.

  • Single Responsibility Principle (SRP) – Each decorator has one specific responsibility.


5. Where is the Decorator Pattern used in .NET?

Common examples include:

  • ASP.NET Core Middleware

  • Stream classes (BufferedStream, CryptoStream, GZipStream)

  • Logging pipelines

  • Caching layers

  • Validation and auditing decorators


6. What is the difference between Decorator and Proxy?

  • Decorator enhances an object's behavior.

  • Proxy controls access to an object, such as lazy loading, security, or remote communication.


Summary

The Decorator Design Pattern is one of the most flexible and widely used structural patterns in modern software development. By wrapping objects with additional functionality at runtime, it allows applications to evolve without modifying existing classes or creating an excessive number of subclasses.

In C# and ASP.NET Core, the Decorator Pattern is used extensively in middleware pipelines, stream classes, logging frameworks, validation layers, caching mechanisms, and auditing solutions. Mastering this pattern helps you build modular, maintainable, and extensible applications while adhering to key SOLID principles and the philosophy of composition over inheritance.


Coming Up Next: Part 3.5 – Facade Design Pattern

In the next article, we'll explore the Facade Design Pattern, including:

  • What is the Facade Pattern?

  • Why complex subsystems need simplification

  • Facade Pattern Structure

  • UML Class Diagram

  • Complete C# Console Application

  • ASP.NET Core implementation

  • Real-world examples (Banking System, Home Theater, E-commerce Checkout)

  • Advantages and disadvantages

  • Best practices

  • Common mistakes

  • Facade vs Adapter vs Mediator

  • Interview questions

You'll learn how the Facade Pattern provides a simple, unified interface to complex subsystems, making enterprise applications easier to use, maintain, and extend.

Composite Design Pattern

 Mastering Design Patterns in C# and ASP.NET Core

Part 3.3 – Composite Design Pattern

Series: Design Patterns in C# and ASP.NET Core
Pattern Category: Structural Design Pattern
Difficulty: ⭐⭐⭐⭐☆ (Intermediate)
Prerequisites: OOP Concepts, Interfaces, Recursion, Collections, Dependency Injection


Table of Contents

  1. Introduction

  2. What is the Composite Design Pattern?

  3. Why Do We Need the Composite Pattern?

  4. The Problem with Hierarchical Structures

  5. Real-World Analogy

  6. Composite Pattern Structure

  7. UML Class Diagram

  8. Components of the Composite Pattern

  9. Complete C# Console Application

  10. ASP.NET Core Implementation

  11. Real-World Examples

  12. Advantages

  13. Disadvantages

  14. Best Practices

  15. Common Mistakes

  16. Composite vs Decorator vs Flyweight

  17. Interview Questions

  18. Summary


Introduction

Many real-world applications deal with hierarchical structures, where individual objects and groups of objects need to be treated in the same way.

Examples include:

  • File systems (Folders and Files)

  • Organization charts (Managers and Employees)

  • Menu systems (Menus and Menu Items)

  • Product categories

  • HTML/XML DOM trees

  • UI controls (Panels containing Buttons, TextBoxes, etc.)

Without a proper design pattern, developers often write separate logic for individual objects and collections of objects, resulting in duplicated code and increased complexity.

The Composite Design Pattern provides a clean solution by allowing clients to treat individual objects (Leaf nodes) and groups of objects (Composite nodes) uniformly.


What is the Composite Design Pattern?

Definition

The Composite Pattern is a Structural Design Pattern that composes objects into tree structures to represent part-whole hierarchies.

It allows clients to treat individual objects and compositions of objects uniformly.

Simply put:

A single object and a group of objects share the same interface.


Why Do We Need the Composite Pattern?

Imagine a file explorer.

Documents
│
├── Resume.docx
├── Project
│   ├── Design.pdf
│   ├── Code.zip
│   └── Notes.txt
└── Photos
    ├── Image1.jpg
    └── Image2.jpg

Without the Composite Pattern:

  • Files require one set of operations.

  • Folders require another.

  • Recursive traversal becomes complicated.

With the Composite Pattern:

  • Both File and Folder implement the same interface.

  • The client simply calls Display() without worrying about whether it's a file or a folder.


The Problem with Hierarchical Structures

Consider an organization hierarchy.

CEO
 ├── Manager A
 │      ├── Developer 1
 │      └── Developer 2
 │
 └── Manager B
        ├── Tester
        └── Designer

Without Composite:

if(employee is Manager)
{
    // Traverse subordinates
}
else
{
    // Display employee
}

Every new hierarchy requires additional conditional logic.

Composite eliminates these checks by treating every node the same way.


Real-World Analogy

Think of a company organization chart.

  • An Employee may work individually.

  • A Manager is also an employee but manages a team.

Both are employees.

Similarly:

  • A File is an item.

  • A Folder is also an item that contains other items.

The client interacts with both using the same interface.


Composite Pattern Structure

The Composite Pattern consists of:

  • Component – Common interface

  • Leaf – Individual object

  • Composite – Collection of components

  • Client – Uses the component interface


UML Class Diagram

                    +----------------------+
                    |     Component        |
                    +----------------------+
                    | + Display()          |
                    +----------^-----------+
                               |
          +--------------------+--------------------+
          |                                         |
+----------------------+              +----------------------+
|       File           |              |      Folder          |
+----------------------+              +----------------------+
| Display()            |              | Add()               |
|                      |              | Remove()            |
+----------------------+              | Display()           |
                                      +---------+-----------+
                                                |
                                     Contains many Components

Components of the Composite Pattern

Component

Defines the common interface.

public interface IFileSystem
{
    void Display();
}

Leaf

Represents an individual object.

public class File : IFileSystem
{
    public string Name { get; }

    public File(string name)
    {
        Name = name;
    }

    public void Display()
    {
        Console.WriteLine($"File : {Name}");
    }
}

Composite

Represents a collection of components.

public class Folder : IFileSystem
{
    public string Name { get; }

    private readonly List<IFileSystem> items = new();

    public Folder(string name)
    {
        Name = name;
    }

    public void Add(IFileSystem item)
    {
        items.Add(item);
    }

    public void Remove(IFileSystem item)
    {
        items.Remove(item);
    }

    public void Display()
    {
        Console.WriteLine($"Folder : {Name}");

        foreach (var item in items)
        {
            item.Display();
        }
    }
}

Complete C# Console Application

class Program
{
    static void Main()
    {
        Folder root = new Folder("Documents");

        root.Add(new File("Resume.docx"));
        root.Add(new File("Profile.pdf"));

        Folder project = new Folder("Project");

        project.Add(new File("Design.pdf"));
        project.Add(new File("Code.zip"));

        root.Add(project);

        root.Display();
    }
}

Output

Folder : Documents
File : Resume.docx
File : Profile.pdf
Folder : Project
File : Design.pdf
File : Code.zip

Notice that both File and Folder are treated exactly the same through the IFileSystem interface.


ASP.NET Core Implementation

Suppose you're building a dynamic navigation menu.

Component

public interface IMenuComponent
{
    string Render();
}

Leaf

public class MenuItem : IMenuComponent
{
    public string Name { get; set; }

    public string Render()
    {
        return $"Menu : {Name}";
    }
}

Composite

public class MenuGroup : IMenuComponent
{
    public string Name { get; set; }

    private readonly List<IMenuComponent> menus = new();

    public void Add(IMenuComponent menu)
    {
        menus.Add(menu);
    }

    public string Render()
    {
        StringBuilder builder = new();

        builder.AppendLine(Name);

        foreach (var menu in menus)
        {
            builder.AppendLine(menu.Render());
        }

        return builder.ToString();
    }
}

The Composite Pattern makes it easy to render nested menus regardless of depth.


Real-World Examples

The Composite Pattern is ideal for representing tree-like structures.

File Explorer

Folder
    File
    File
    Folder
        File

Organization Hierarchy

CEO
    Manager
        Developer
        Tester

HTML DOM

html
 ├── head
 ├── body
 │      ├── div
 │      ├── button
 │      └── table

Product Categories

Electronics
    Laptop
    Mobile
    TV

Menu Systems

Admin
    Users
    Roles
    Permissions

UI Controls

Panel
    Button
    Label
    TextBox

Advantages

  • Treats individual objects and collections uniformly.

  • Simplifies client code.

  • Supports recursive tree structures naturally.

  • Makes adding new component types easier.

  • Promotes the Open/Closed Principle.

  • Reduces conditional statements.

  • Improves maintainability.


Disadvantages

  • Can make the design overly generic.

  • Difficult to restrict which components can contain children.

  • Deep hierarchies may affect performance.

  • Debugging recursive structures can be more challenging.


Best Practices

  • Use Composite when representing part-whole hierarchies.

  • Keep the Component interface simple and focused.

  • Use recursion carefully to avoid stack overflows with extremely deep trees.

  • Hide collection management from the client when possible.

  • Apply Dependency Injection for composite services in ASP.NET Core.


Common Mistakes

Adding Child Methods to Leaf Objects

Leaf objects should not expose Add() or Remove() methods if they cannot contain children.


Overusing Composite

Not every parent-child relationship requires the Composite Pattern. Use it only when clients need to treat single objects and groups uniformly.


Mixing Business Logic

Composite classes should focus on managing child components, not unrelated business operations.


Ignoring Performance

Very deep recursive hierarchies may require optimization or iterative traversal.


Composite vs Decorator vs Flyweight

FeatureCompositeDecoratorFlyweight
PurposeRepresent tree structuresAdd behavior dynamicallyShare objects to reduce memory
Uses Recursion✅ Yes❌ No❌ No
Parent–Child Relationship✅ Yes❌ No❌ No
Object Sharing❌ No❌ No✅ Yes
Runtime Behavior Extension❌ No✅ Yes❌ No

Interview Questions

1. What is the Composite Design Pattern?

A structural design pattern that composes objects into tree structures, allowing clients to treat individual objects and groups uniformly.


2. When should you use the Composite Pattern?

When working with hierarchical structures such as folders, menus, organization charts, or UI controls.


3. What is the difference between a Leaf and a Composite?

  • Leaf represents an individual object and cannot contain children.

  • Composite represents a group of objects and can contain both Leaf and Composite objects.


4. Which SOLID principle does the Composite Pattern support?

The Open/Closed Principle (OCP) because new component types can be introduced without modifying existing client code.


5. Is recursion commonly used in the Composite Pattern?

Yes. Recursive traversal is one of the key characteristics of the Composite Pattern.


6. What are common real-world examples?

  • File systems

  • HTML DOM

  • Organization charts

  • Product categories

  • Navigation menus

  • UI component hierarchies


Summary

The Composite Design Pattern is one of the most effective ways to model hierarchical data structures. By allowing individual objects and groups of objects to share a common interface, it simplifies client code, reduces conditional logic, and makes recursive operations straightforward.

In enterprise C# and ASP.NET Core applications, the Composite Pattern is widely used for file systems, menu structures, organization hierarchies, UI components, and document object models. Mastering this pattern will help you design scalable, maintainable applications that work naturally with tree-like data.


Coming Up Next: Part 3.4 – Decorator Design Pattern

In the next article, we'll explore the Decorator Design Pattern, including:

  • What is the Decorator Pattern?

  • Why inheritance is not always the best choice

  • Dynamically adding behavior to objects

  • Decorator Pattern Structure

  • UML Class Diagram

  • Complete C# Console Application

  • ASP.NET Core implementation

  • Real-world examples (Coffee Shop, Notification System, ASP.NET Core Middleware)

  • Advantages and disadvantages

  • Best practices

  • Common mistakes

  • Interview questions

You'll learn how the Decorator Pattern enables you to extend object functionality dynamically without modifying existing classes, making it one of the most widely used structural patterns in modern C# and ASP.NET Core applications.

Don't Copy

Protected by Copyscape Online Plagiarism Checker