This Is How C# Composite Pattern Can Help Your Code


In software engineering, a design pattern is a proven strategy for solving a common problem. Design patterns are used to structure and modularize code, to promote testability and readability, or to offer other benefits such as increased reliability or performance.

The Composite design pattern is one of those.

In this article, you will learn about the Composite design pattern and its implementation in C# with a real-world example.

What is the Composite design pattern?

composite design pattern in a nutshell

The Composite design pattern is a structural design pattern that allows for the treatment of individual objects and compositions of objects in the same way. This pattern is often used when building data structures or user interfaces.

This pattern allows you to arrange the objects in a tree-like structure and treat them as a single object.

In this pattern, all the objects represent a node and have some task to perform.

In the Composite pattern, there is a tree structure in which the leaf represents the primitive types that cannot have children, whereas the nodes represent the composite classes that can have another composite class as a child or a leaf.

When to Use the Composite design pattern?

The Composite design pattern is extensively used in C# code, especially to represent the hierarchies of UI components or the code that deals with graphs.

However, here are some scenarios where you should consider using the Composite pattern:

  • When you have a hierarchy of objects, and you need to treat all objects the same.
  • When the client does not recognize whether it is dealing with a single object or composite objects, it must perform the same operation on all objects.

How to implement Composite design pattern in C# – Structural code

To understand the structural code better, you must know the participants of the Composite design pattern. Four participants make up this pattern:

  • Component (Interface/ abstract class): An abstract class or interface that defines a common functionality for all the objects in the hierarchy.
  • Leaf (Primitive class): A class with no subclasses. It defines the functionality for primitive objects in the hierarchy.
  • Composite (Concrete class): A concrete class that defines the behavior of child components.
  • Client: Uses Component interface to manipulate objects in the composition.

Here is the structural code for the Composite design pattern.

public abstract class Component
{
    protected string _name;

    public Component(string name)
    {
        _name = name;
    }

    public abstract void Add(Component component);

    public abstract void Remove(Component component);

    public abstract void Display(int depth);
}

public class Composite : Component
{
    List<Component> _childs = new List<Component>();

    public Composite(string name)
        : base(name)
    {
    }

    public override void Add(Component component)
    {
        _childs.Add(component);
    }

    public override void Remove(Component component)
    {
        _childs.Remove(component);
    }

    public override void Display(int depth)
    {
        Console.WriteLine(new String('-', depth) + _name);
        // Recursively display child nodes
        foreach (Component component in _childs)
        {
            component.Display(depth + 2);
        }
    }
}

//Concrete class to add and remove leaves
public class Leaf : Component
{
    public Leaf(string name)
        : base(name)
    {
    }

    public override void Add(Component component)
    {
        Console.WriteLine("Cannot add to a leaf");
    }

    public override void Remove(Component component)
    {
        Console.WriteLine("Cannot remove from a leaf");
    }

    public override void Display(int depth)
    {
        Console.WriteLine(new String('-', depth) + _name);
    }
}

And the usage:

Composite root = new Composite("root");

//Adding three leaves that have no children
root.Add(new Leaf("Leaf 1"));
root.Add(new Leaf("Leaf 2"));
root.Add(new Leaf("Leaf 3"));

//Adding a Composite leaf with 3 children leaves
Composite comp = new Composite("Composite X");
comp.Add(new Leaf("Leaf X1"));
comp.Add(new Leaf("Leaf X2"));
comp.Add(new Leaf("Leaf X3"));

//Adding the composite leaf and its children to root
root.Add(comp);

// Add and remove a leaf through leaf class            
Leaf leaf = new Leaf("Leaf D");
root.Add(leaf);

// Display the tree
root.Display(1);
root.Remove(leaf);
Console.Write("\n\n\n\nAfter removing leaf\n");
root.Display(1);

The output:

-root
---Leaf 1
---Leaf 2
---Leaf 3
---Composite X
-----Leaf X1
-----Leaf X2
-----Leaf X3
---Leaf D




After removing leaf
-root
---Leaf 1
---Leaf 2
---Leaf 3
---Composite X
-----Leaf X1
-----Leaf X2
-----Leaf X3

Composite pattern – real-world example in C#

In the following code example, you will see object hierarchies of shareholders.

//The root node i.e. Component
public abstract class EmployeeComponent
{
    protected string _name;
    protected string _designation;

    public EmployeeComponent(string name, string designation)
    {
        _name = name;
        _designation = designation;
    }

    public abstract void AddEmployee(EmployeeComponent employee);
    public abstract void RemoveEmployee(EmployeeComponent employee);
    public abstract void ShowEmployee(int level);
}


//Leaf Class
public class EmployeePrimitive : EmployeeComponent
{

    public EmployeePrimitive(string name,
                             string designation) : base(name, designation) { }
    
    //Leaf cannot have children
    public override void AddEmployee(EmployeeComponent em)
    {
        Console.WriteLine("Primitive Element does not have children");
    }

    //Leaf do not have children 
    //So nothing can be removed
    public override void RemoveEmployee(EmployeeComponent em)
    {
        Console.WriteLine("Can't remove from Primitive Element");
    }

    //Display the root node
    public override void ShowEmployee(int level)
    {
        Console.WriteLine(new String('*', level) + " " + _name + " " + _designation);
    }
}

//Composite class
public class CompositeEmployee : EmployeeComponent
{
    List<EmployeeComponent> _employees = new List<EmployeeComponent>();

    public CompositeEmployee(string name,
                             string designation) : base(name, designation) { }

    //Add employee
    public override void AddEmployee(EmployeeComponent employee)
    {
        _employees.Add(employee);
    }

    //Remove employee
    public override void RemoveEmployee(EmployeeComponent em)
    {
        _employees.Remove(em);
    }

    //Show the tree structure
    public override void ShowEmployee(int Emp_level)
    {
        Console.WriteLine(new String('*', Emp_level) + " ! " + _name + " " + _designation);

        foreach (EmployeeComponent e in _employees)
        {
            e.ShowEmployee(Emp_level + 2); //displaying the level of hierarchy
        }
    }
}

And the usage:

//Creating the tree structure
//Level 1
CompositeEmployee ce = new CompositeEmployee("John", "Owner"); //The root node
ce.AddEmployee(new EmployeePrimitive("Smith", "Designer"));
ce.AddEmployee(new EmployeePrimitive("Jacquline", "Developer"));

//Level 2
CompositeEmployee ce2 = new CompositeEmployee("Shareholders", "Shareholders");
ce2.AddEmployee(new EmployeePrimitive("Alison", "shareholder 1"));
ce2.AddEmployee(new EmployeePrimitive("Wilson", "shareholder 2"));

ce.AddEmployee(ce2);

ce.ShowEmployee(1);

produces the following output.

* ! John Owner
*** Smith Designer
*** Jacquline Developer
*** ! Shareholders Shareholders
***** Alison shareholder 1
***** Wilson shareholder 2

Benefits and drawbacks of using the Composite pattern:

Like every other design pattern, the Composite pattern has its pros and cons:

Benefits:

  • You achieve uniformity by using it.
  • Helps you work with complex tree structures easily.
  • Follows the Open/closed principle, i.e., you can add more components without changing the existing code.
  • Helps you achieve the desired functionality without worrying about the type of object you are dealing with.

Drawbacks:

  • Sometimes it becomes difficult to define a standard interface for the classes with many different functionalities. To do so, you need to overgeneralize the components, making it difficult to restrict objects included in the composite group.
  • The client must recognize the composite, and non-composite objects as the composite class extends to provide access to its child nodes.

What are related patterns to the Composite design pattern?

The Composite pattern relates to many other patterns based on the structure, functionality, or behavior:

  • You can use the Builder pattern to create complex Composite trees by programming its construction steps recursively.
  • Composite and Chain of Responsibility are often combined when the leaf component needs to pass a request through the chain of all the components down to the root.
  • You can go through Composite trees through the Iterator pattern.
  • You can use the Visitor pattern when performing certain operations on the entire Composite tree.
  • Decorator and Composite share the same structural diagram as they rely on the composition of an open-ended number of objects. However, the Decorator can have only one child component.

Conclusion

The Composite design pattern helps you treat a group of objects as a single entity and achieve uniformity. You should use it when you have object hierarchies, and the client must deal with a single object or a group of objects similarly.

It is related to many design patterns such as Decorator, Proxy, Chain of Responsibility, Visitor, etc. Composite pattern is extensively used in software engineering due to its benefits.

If you want to learn more about design patterns, check the separate in-depth article about design patterns in C#.

Recent Posts