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
Introduction
What is the Composite Design Pattern?
Why Do We Need the Composite Pattern?
The Problem with Hierarchical Structures
Real-World Analogy
Composite Pattern Structure
UML Class Diagram
Components of the Composite Pattern
Complete C# Console Application
ASP.NET Core Implementation
Real-World Examples
Advantages
Disadvantages
Best Practices
Common Mistakes
Composite vs Decorator vs Flyweight
Interview Questions
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
| Feature | Composite | Decorator | Flyweight |
|---|---|---|---|
| Purpose | Represent tree structures | Add behavior dynamically | Share 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.
No comments:
Post a Comment