Menu
Coddy logo textTech

Composite Pattern

Part of the Object Oriented Programming section of Coddy's C# journey — lesson 61 of 70.

The Composite pattern is a structural pattern that lets you compose objects into tree structures and treat individual objects and groups of objects uniformly. It's perfect for representing part-whole hierarchies where you want to work with both single items and collections through the same interface.

The pattern has three key components: a component interface that defines common operations, leaf classes representing individual objects, and composite classes that contain children and implement the same interface:

public interface IFileSystemItem
{
    string Name { get; }
    int GetSize();
}

public class File : IFileSystemItem
{
    public string Name { get; }
    private int _size;
    
    public File(string name, int size)
    {
        Name = name;
        _size = size;
    }
    
    public int GetSize() => _size;
}

public class Folder : IFileSystemItem
{
    public string Name { get; }
    private List<IFileSystemItem> _items = new List<IFileSystemItem>();
    
    public Folder(string name) => Name = name;
    
    public void Add(IFileSystemItem item) => _items.Add(item);
    
    public int GetSize() => _items.Sum(item => item.GetSize());
}

The beauty of this pattern is that clients don't need to know whether they're working with a single file or an entire folder hierarchy:

var root = new Folder("root");
root.Add(new File("doc.txt", 100));

var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));

root.Add(subFolder);

Console.WriteLine(root.GetSize());  // 650

The Composite pattern is ideal for file systems, UI component trees, organization charts, or any structure where containers and contents should be treated the same way. It simplifies client code by eliminating the need to distinguish between simple and complex elements.

challenge icon

Challenge

Easy

Let's build an organization chart system using the Composite pattern. You'll create a structure where both individual employees and departments (which contain employees or other departments) can be treated uniformly, allowing you to calculate the total salary cost at any level of the hierarchy.

You'll organize your code across three files:

  • OrgComponent.cs: Define an IOrgComponent interface in the Organization namespace with two members: a Name property (string) and a GetSalary() method that returns an integer. This interface represents the common contract that both individual employees and departments will follow.
  • OrgClasses.cs: Create two classes in the same namespace:
    • Employee - a leaf class representing an individual worker. It takes a name and salary in its constructor and implements the interface by returning its own salary.
    • Department - a composite class representing a group. It takes a name in its constructor and maintains a list of IOrgComponent items. Include an Add(IOrgComponent component) method to add members. Its GetSalary() should return the sum of all its members' salaries.
  • Program.cs: Build an organization structure based on input and calculate the total salary. You'll create departments and employees, nest them appropriately, and demonstrate how the composite pattern lets you treat the entire hierarchy uniformly.

You will receive the following inputs:

  • The number of components to create
  • For each component: the type (employee or department), followed by the name, and for employees, their salary. For departments, you'll also receive the parent department name (or root if it's a top-level department)

Input format for each component:

employee
{name}
{salary}
{parent_department}

department
{name}
{parent_department}

After building the structure, print the total salary of the root department with the format {root_name} Total Salary: {amount}.

For example, if the inputs are:

5
department
Engineering
root
employee
Alice
5000
Engineering
employee
Bob
4500
Engineering
department
QA
Engineering
employee
Charlie
4000
QA

The output should be:

Engineering Total Salary: 13500

The Engineering department contains Alice (5000), Bob (4500), and the QA department. QA contains Charlie (4000). When you call GetSalary() on Engineering, it recursively calculates the total from all nested components - demonstrating how the Composite pattern lets you work with complex tree structures through a simple, uniform interface!

Cheat sheet

The Composite pattern is a structural pattern that composes objects into tree structures and treats individual objects and groups uniformly through the same interface. It's ideal for part-whole hierarchies.

The pattern has three key components:

  • Component interface: Defines common operations
  • Leaf classes: Represent individual objects
  • Composite classes: Contain children and implement the same interface

Basic implementation example:

// Component interface
public interface IFileSystemItem
{
    string Name { get; }
    int GetSize();
}

// Leaf class
public class File : IFileSystemItem
{
    public string Name { get; }
    private int _size;
    
    public File(string name, int size)
    {
        Name = name;
        _size = size;
    }
    
    public int GetSize() => _size;
}

// Composite class
public class Folder : IFileSystemItem
{
    public string Name { get; }
    private List<IFileSystemItem> _items = new List<IFileSystemItem>();
    
    public Folder(string name) => Name = name;
    
    public void Add(IFileSystemItem item) => _items.Add(item);
    
    public int GetSize() => _items.Sum(item => item.GetSize());
}

Usage - clients work with single items and collections uniformly:

var root = new Folder("root");
root.Add(new File("doc.txt", 100));

var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));

root.Add(subFolder);

Console.WriteLine(root.GetSize());  // 650

The Composite pattern is useful for file systems, UI component trees, organization charts, or any structure where containers and contents should be treated the same way.

Try it yourself

using System;
using System.Collections.Generic;
using Organization;

class Program
{
    public static void Main(string[] args)
    {
        int n = Convert.ToInt32(Console.ReadLine());
        
        // TODO: Create a dictionary to store departments by name
        // This will help you find parent departments when adding components
        Dictionary<string, Department> departments = new Dictionary<string, Department>();
        
        // TODO: Keep track of the root department
        Department root = null;
        
        for (int i = 0; i < n; i++)
        {
            string type = Console.ReadLine();
            
            if (type == "department")
            {
                string name = Console.ReadLine();
                string parent = Console.ReadLine();
                
                // TODO: Create the department
                // TODO: Add it to the departments dictionary
                // TODO: If parent is "root", this is the root department
                // TODO: Otherwise, find the parent department and add this department to it
            }
            else if (type == "employee")
            {
                string name = Console.ReadLine();
                int salary = Convert.ToInt32(Console.ReadLine());
                string parent = Console.ReadLine();
                
                // TODO: Create the employee
                // TODO: Find the parent department and add the employee to it
            }
        }
        
        // TODO: Print the total salary of the root department
        // Format: "{root_name} Total Salary: {amount}"
        Console.WriteLine($"{root.Name} Total Salary: {root.GetSalary()}");
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming