C# Composite Pattern

Summary: in this tutorial, you’ll learn how to use the C# Composite pattern to treat individual objects and compositions of objects uniformly.

Introduction to the C# composite pattern

The C# Composite pattern is a structural design pattern that allows you to create a hierarchy of individual objects and collections of objects through a shared interface. This enables the client to treat individual objects and the group of objects uniformly.

Since an individual object and a collection of objects share a common interface, the client doesn’t need to know where it is managing a single object or a collection object.

Instead, the client can make use of polymorphism without having to apply unnecessary condition statements that check whether the object is an individual object or a collection of objects.

To illustrate this composite pattern, imagine you have to manage a bill of materials (BOM) that contains a list of materials for making a part of a product (assembly) or the whole product.

In some cases, you need to perform the same operation on a component whether it is a single component or an assembly that consists of individual components.

For example, you should be able to get the cost of a component without knowing whether it is a component or an assembly.

The following UML diagram illustrates the composite pattern:

C# composite pattern

In this diagram:

  • IComponent: This is a shared interface that specifies the operation each component needs to implement.
  • Leaf: This is an individual component.
  • Composite: This is a collection of components. It can include both leaves and other composites.
  • Client: This class has access to different components via the shared interface IComponent, taking advantage of polymorphism.

C# Composite pattern example

The following C# program demonstrates the composite pattern:

// Shared interface
public interface IComponent
{
    string Name { get; set; }
    int Quantity { get; set; }
    double GetCost();
}

// Leaf component
public class Part : IComponent
{
    public string Name { get; set; }
    public int Quantity { get; set; }
    public double Cost { get; set; }
    public Part(string name, int quantity, double cost)
    {
        Name = name;
        Quantity = quantity;
        Cost = cost;
    }

    public double GetCost() => Cost * Quantity;
}

// Composite component
public class Assembly : IComponent
{
    public string Name {  get; set; }
    public int Quantity { get; set; }

    private readonly List<IComponent> _components = new();

    public Assembly(string name, int quantity, IEnumerable<IComponent> components)
    {
        Name = name;
        Quantity = quantity;
        _components.AddRange(components);
    }

    public double GetCost() => _components.Sum(component => component.GetCost());
}

// Client
public class Program
{
    public static void Main(string[] args)
    {

        // parts
        var engine = new Part("Engine", 1, 5000.0);
        var tires = new Part("Tires", 4, 1000.0);

        // assembly
        var body = new Assembly(
            "Body", 
            1, 
            new List<IComponent> {
                new Part("Frame", 1, 2000.0),
                new Part("Doors", 4, 1000.0),
                new Part("Windows", 6, 500.0)
            }
        );


        // car
        var car = new Assembly(
            "Car", 
            1, 
            new List<IComponent> {
                engine,
                tires,
                body
        });

        // Calculate the cost of the car
        var carCost = car.GetCost();

        Console.WriteLine($"The cost of the car is: {carCost:C}");
    }
}
Code language: C# (cs)

Output:

The cost of the car is: $18,000.00Code language: C# (cs)

How it works.

First, define a shared interface IComponent that both component and assembly need to implement. The IComponent interface has the Name and Quantity properties as well as the GetCost() method:

public interface IComponent
{
    string Name { get; set; }
    int Quantity { get; set;}

    double GetCost();
}Code language: C# (cs)

Second, define the Part class that serves as a leaf. The Part class implements the IComponent interface:

public class Part : IComponent
{
    public string Name { get; set; }
    public int Quantity {  get; set; }
    public double Cost { get; set; }

    public Part(string name, int quantity, double cost)
    {
        Name = name;
        Quantity = quantity;
        Cost = cost;
    }

    public double GetCost() => Cost * Quantity;
}Code language: C# (cs)

In the Part class, the GetCost() calculates the cost by multiplying the cost of each component by the quantity.

Third, define the Assembly class that serves as the composite. The Assembly class implements the IComponent interface and has a field with the type of IEnumerable<IComponent> that represents a list of IComponent:

public class Assembly : IComponent
{
    public string Name {  get; set; }
    public int Quantity { get; set; }

    private readonly List<IComponent> _components = new();

    public Assembly(string name, int quantity, IEnumerable<IComponent> components)
    {
        Name = name;
        Quantity = quantity;
        _components.AddRange(components);
    }

    public double GetCost() => _components.Sum(component => component.GetCost());
}Code language: C# (cs)

The GetCost() method calculates the total cost by using the Sum extension method by adding up the costs of all the components in the _component list.

Finally, define the Program class that uses the Part and Assembly classes to construct a Car product and call the GetCost() of the car object to get the total cost of the car:

public class Program
{
    public static void Main(string[] args)
    {

        // parts
        var engine = new Part("Engine", 1, 5000.0);
        var tires = new Part("Tires", 4, 1000.0);

        // assembly
        var body = new Assembly("Body", 1, new List<IComponent> {
            new Part("Frame", 1, 2000.0),
            new Part("Doors", 4, 1000.0),
            new Part("Windows", 6, 500.0)
        });

        // car
        var car = new Assembly("Car", 1, new List<IComponent>{
            engine,
            tires,
            body
        });

        // Calculate the cost of the car
        var carCost = car.GetCost();

        Console.WriteLine($"The cost of the car is: {carCost:C}");
    }
}Code language: C# (cs)

Summary

  • Use the Composite pattern to treat individual objects and compositions of objects uniformly.
Was this tutorial helpful ?