Design Patterns Software Engineering

Design Patterns – Composite

Pinterest LinkedIn Tumblr
 The code is available on GitHub here

The Composite Pattern is a design pattern that enables the composition of objects in a hierarchical tree-like structure, and manages them uniformly. This pattern is particularly useful when you have a hierarchy of objects with different characteristics, and you need to access them uniformly. Each object can contain compositions of other objects or be a leaf node. However, they all share the same interface, making them interchangeable with one another.

When to use

Use the Composite Pattern if you have a hierarchy of different objects that each of them need to be accessed uniformly

There are many cases where objects need to form a tree like structure. Also, each object may have some common characteristics with each other. For example, folders and files can form a hierarchy. Folders can contain many files or other folders, while files cannot. Despite folders and files being different objects, they share some common characteristics, such as name and size. If a client needs to access the size (or any other attribute ) of a node without caring whether it is a folder or a file, then folders and files can share a common interface, allowing the client to manage the objects of the hierarchy uniformly.

Use the Composite Pattern if you would like to iterate over a collection of objects

In many cases you may want to iterate over a collection ( or a hierarchy ) of different objects and apply some calculation for each of the iterated objects. By using the Composite Pattern, you can easily make the collection of different objects iteratable despite the different objects and composites (objects that contain other objects) it may contain.

Use the Composite Pattern if you would like to apply the same operations to both composites and leaf objects.

In some cases, you may want to ignore the differences among the objects of a hierarchy. For instance, suppose you want to have an AddNode operation in every node of the hierarchy, whether it is a folder or a file. Even though adding a node to a file is not valid, you may still want to design your system to ignore invalid operations for the sake of simplicity and uniformity for the client. The Composite Pattern can help hide this complexity and provide a simpler interface for managing the structure.

composite hierarchy 2

How to implement

First, we define the main abstract class or interface that all objects, both composites and leaf nodes will extend.

This class or interface is called the Component. The Component class is the main interface for external systems to interact with the objects in the hierarchy. It may contain many operations that may or may not be applicable for all the object types in the hierarchy. This is a design decision and usually depends on the requirements and usage.

The IComponent interface below demonstrates the basic structure of a Component class.

public interface IComponent
{
   void Add(IComponent component); // Adds another component to the composite.
   void Remove(IComponent component);// Removes a component from the composite.
   void AnotherOperation();

   // can also contain other operations.
}

Then, we separate the composite objects from the leaf objects and implement the Component interface accordingly.

The Composite Pattern consists of two main types of components. The first is the composite objects which can contain other components, and the others are the leaf components which are terminal nodes and cannot contain other objects. Regardless of their type, all object types should implement the IComponent interface accordingly.

The Composite and Leaf classes below demonstrate how to implement these two types of components.

public class Composite : IComponent
{
   private List<IComponent> _children = new();

   public void Add(IComponent component) => _children.Add(component);
   public void Remove(IComponent component) => _children.Remove(component);

   public void AnotherOperation()
   {
      // do some computation.
   }
}

public class Leaf : IComponent
{
   public void Add(IComponent component){ /* do nothing */}
   public void Remove(IComponent component){ /* do nothing */}

   public void AnotherOperation()
   {
      // do some computation.
   }
}

Finally, create the client code that uses the Composite and Leaf objects to build the part-whole hierarchy. The client code can use the Component interface to treat the composite and leaf objects uniformly.

The client code can use the Component interface to treat the composite and leaf objects uniformly. You can build any hierarchy by using the Composite and Leaf components. The code snippet below demonstrates how to construct a tree-like structure and call operations in the components.

// Construct the tree like structure.
var root = new Composite();
var subDir1 = new Composite();
var subDir2 = new Composite();
var file1 = new Leaf();
var file2 = new Leaf();

// Adding children to the composite components.
root.Add(subDir1);
root.Add(subDir2);
subDir1.Add(file1);
subDir2.Add(file2);

// calling operations in the components.
root.AnotherOperation();

Composite Pattern – Class diagram

In Composite Design Pattern class diagram the abstract Component hides all the different composite and leaf objects of the hierarchy and gives to the client a unified interface to interact with.

composite class diagram
Composite Pattern class diagram

Real World Example

The Composite Pattern is used in many frameworks and programming languages across the spectrum. An example of this is in Windows Presentation Framework (WPF), which is a way to create user interfaces using C#. In WPF, every element that can be drawn is a UIElement. The UIElement is the main Component interface that all UI Elements implement in order to be rendered. Each element that is a composite should also implement another interface, the UIElementCollection. The UIElementCollection converts a component of the hierarchy into a composite. In WPF, the approach is to extend the UIElementCollection instead of having the Add, Remove, and other composite related functions in the Component interface itself (UIElement).

public class UIElementCollection
{
   public UIElementCollection(UIElement visualParent, FrameworkElement logicalParent);

   // The actual number of elements in the collection.
   public virtual int Count { get; }
   
   // Adds the specified element to the System.Windows.Controls.UIElementCollection.
   public virtual int Add(UIElement element);
   
   // Removes all elements from a System.Windows.Controls.UIElementCollection.
   public virtual void Clear();
   
   // Removes the specified element from a System.Windows.Controls.UIElementCollection.
   public virtual void Remove(UIElement element);
}

In WPF, many UIElements, such as StackPanel and DockPanel, implement the UIElementCollection, making them composites and providing them with the functionality to hold other children. Other elements, such as Buttons, Labels, and TextBlock, are not composites, so they implement only the UIElement.

To illustrate this, we create a StackPanel object that holds two child elements: a Button and a TextBlock. We then add the child elements to the panel using the Children property, which is a collection of UIElement objects. Finally, we set the Content property of the MainWindow object to the StackPanel object, which causes the panel and its child elements to be displayed on the screen.

public partial class MainWindow : Window
{
   public MainWindow()
   {
      InitializeComponent();

      // Create a panel to hold multiple child elements. A StackPanel is a UIElementCollection thus making it a composite.
      StackPanel panel = new StackPanel();

      // Create two child elements
      Button button = new Button { Content = "Click me!" };
      TextBlock textBlock = new TextBlock { Text = "Hello, world!" };

      // Add the child elements to the panel
      panel.Children.Add(button);
      panel.Children.Add(textBlock);

      // Add the panel to the main window
      Content = panel;
   }
}

When we run this program, it displays a window with a button and a text block.

This is just a simple example, but it demonstrates how the UIElement hierarchy is used in a real-world application to build complex user interfaces by creating a hierarchy of simple and composite visual elements.

Example – File / Directory Hierarchy

This section presents the implementation of a tree like structure that contains Directories and Files. A Directory can contain more directories and/or files, while a File, as a leaf node, cannot contain any children. Each component of the structure has a name and size, and you can search a component from the composite by its name. Additionally, you can display a composite hierarchy in the console.

The Component interface

The first step in implementing the file/directory hierarchy is to design the main interface that abstracts all the different node types (Directory and File). The interface will provide a unified interface for the client to interact with. The interface, known as IComponent, has the following implementation:

public interface IComponent
{
   string GetName();
   int GetSize();
   void Add(IComponent component); // Adds a child component to the composite object
   void Remove(IComponent component); // Removes a child component from the composite object
   void Display(int depth = 0); // Displays the component hierarchy.
   IComponent Search(String name); // Searches for a component by its name and returns it
}

All different components have a name and size, so the GetName and GetSize methods will be implemented for both of them. The Add() and Remove() methods are used to add and remove children from the composite objects (Directories). In the case of files, those methods are not applicable. The Display method gets an additional depth parameter to track the depth of each component in the file system hierarchy to display the items in the console with their proper indentation. Lastly, the Search method returns the component found (whether is File or Directory) by its name.

The File leaf node

The File component cannot contain other children, and it contains its data in the form of an array of bytes and a name. The File class implements the IComponent interface as follows:

public class File : IComponent
{
   private string _name;
   private byte[] _data;

   public File(string name, byte[] data)
   {
      _name = name;
      _data = data;
   }

   public string GetName() => _name;

   public int GetSize() => _data.Length;

   public void Add(IComponent component)
   { /* Do nothing - files cannot have children */ }

   public void Remove(IComponent component)
   { /* Do nothing - files cannot have children */ }

   public void Display(int depth = 0)
   {
      string indentation = new string('-', depth);
      Console.WriteLine($"{indentation}{_name} ({GetSize()} bytes)");
   }

   public IComponent Search(string name) => null;
}

The Display method is implemented using the depth parameter to indicate what level in the hierarchy the File is located. The Search method is not applicable because there are no children to search for, so we return null. Another approach would be to throw an InvalidOperationException in any not applicable method.

The Directory composite Component

The Directory Composite component is made up of a Directory class, which can contain many directories or files as children. Its attributes, like size, are calculated by its containing objects. A directory does not have a size property by its own. The GetSize method is implemented in such a way that recursively it aggregates all the sizes of the individual children and returns a total size as a result. The Add and Remove methods in this case they add and remove children to the current Directory.

The Search method, similarly with the GetSize, searching the hierachy resursively until it finds a component whose name equals the desired name.

The Display method, besides displaying the name of the current Directory, increases the depth parameter before it is passed as an argument to the Search method of its children. That way, each time we recursively encounter a Directory, we increase the depth, making a nicer display of the tree file structure.

The Directory class implementation is shown below:

public class Directory : IComponent
{
   private string _name;
   private List<IComponent> _children;

   public Directory(string name)
   {
      _name = name;
      _children = new List<IComponent>();
   }

   public string GetName() => _name;

   public int GetSize()
   {
      int size = 0;
      foreach (var component in _children)
         size += component.GetSize();
      return size;
   }

   public void Add(IComponent component) => _children.Add(component);

   public void Remove(IComponent component) => _children.Remove(component);

   public void Display(int depth = 0)
   {
      string indentation = new string('-', depth);
      Console.WriteLine($"{indentation}{_name} (directory)");

      foreach (var component in _children)
         component.Display(depth + 2);
   }

   public IComponent Search(string name)
   {
      foreach (IComponent component in _children)
      {
         if (component.GetName() == name)
         {
            return component;
         }
         else if (component is Directory dir)
            return dir.Search(name);
      }
      return null;
   }
}

Usage Example

Now that we have implemented the Directory Composite Component, we can build a hierarchy with Directories and Files beginning with a root directory and fill it out with different children. Later, we can use operations like Display and Search from the components.

public static void Main()
{
   var root = new Directory("root");
   var subDir1 = new Directory("subdir1");
   var subDir2 = new Directory("subdir2");
   var file1 = new File("file1", new byte[] { 0x33, 0x34 });
   var file2 = new File("file2", new byte[] { 0x33, 0x34, 0x35 });

   root.Add(subDir1);
   root.Add(subDir2);
   subDir1.Add(file1);
   subDir2.Add(file2);

   // Display the contents of the file system
   root.Display();

   // Get the total size of the file system
   int totalSize = root.GetSize();
   Console.WriteLine("Total size of file system: " + totalSize);

   // Search for a file in the file system
   Console.WriteLine("Searching for component:file1");
   var searchResult = root.Search("file1");
   if (searchResult != null)
      Console.WriteLine("Found component: " + searchResult.GetName());
   else
      Console.WriteLine("Component not found.");
   
   // Remove a file from the file system
   subDir1.Remove(file1);
   Console.WriteLine("Removed file1 from subdir1");

   // Display the contents of the file system again
   root.Display();

   // Get the total size of the file system after removal
   totalSize = root.GetSize();
   Console.WriteLine("Total size of file system after removal: " + totalSize);
}

The result of the previous code is shown below:

root (directory)
--subdir1 (directory)
----file1 (2 bytes)
--subdir2 (directory)
----file2 (3 bytes)
Total size of file system: 5
Searching for component:file1
Found component: file1
Removed file1 from subdir1
root (directory)
--subdir1 (directory)
--subdir2 (directory)
----file2 (3 bytes)
Total size of file system after removal: 3

The class diagram of the Directory / File Composite

The final class diagram consists of the Directory and File classes, which implement the IComponent interface. The Directory class, in particular, contains many IComponents, making it a composite node.

Extend the IComponent with a new ImageFile component

The Composite Design Pattern allows us to easily add new IComponent implementations, enhancing the hierarchy’s capabilities without increasing client complexity or requiring changes to other components.

For instance, we can introduce a new ImageFile component, which can store images, by extending the IComponent interface. The ImageFile component is similar to the File component, as both are leaf nodes, with a few minor differences in the IComponent implementation.

public class ImageFile : IComponent
{
   private string _name;
   private int _width;
   private int _height;

   public ImageFile(string name, int width, int height)
   {
      this._name = name;
      this._width = width;
      this._height = height;
   }

   public string GetName() => _name;

   public int GetSize() => _width * _height;

   public void Add(IComponent component)
   { /* Do nothing - files cannot have children */ }

   public void Remove(IComponent component)
   { /* Do nothing - files cannot have children */ }

   public void Display(int depth)
   {
      Console.WriteLine(new string('-', depth) + _name + " [image]");
   }

   public IComponent Search(string name) => null;

   public void Resize(int newWidth, int newHeight)
   {
      Console.WriteLine($"Resizing {_name} from {_width}x{_height} to {newWidth}x{newHeight}");
      _width = newWidth;
      _height = newHeight;
   }
}

The ImageFile component implements the GetSize method differently, by multiplying its dimensions, and the Display method slightly differently to distinguish it from other types. The Add and Remove methods do nothing since the ImageFile component is a leaf node.

With the new ImageFile component, we can add some ImageFile objects to the structure, as shown below:

var root = new Directory("root");
var subDir1 = new Directory("subdir1");
var subDir2 = new Directory("subdir2");
var file1 = new File("file1", new byte[] { 0x33, 0x34 });
var imageFile = new ImageFile("img1", 800, 600);
var imageFile2 = new ImageFile("img2", 800, 600);
var file2 = new File("file2", new byte[] { 0x33, 0x34, 0x35 });

root.Add(subDir1);
root.Add(subDir2);
subDir1.Add(file1);
subDir1.Add(imageFile);
subDir1.Add(imageFile2);
subDir2.Add(file2);

// Display the contents of the file system
root.Display();
// Get the total size of the file system
int totalSize = root.GetSize();
Console.WriteLine("Total size of file system: " + totalSize);

The output would be:

root (directory)
--subdir1 (directory)
----file1 (2 bytes)
----img1 [image]
----img2 [image]
--subdir2 (directory)
----file2 (3 bytes)
Total size of file system: 960005

Using Dependency Injection

Dependency injection is a powerful technique that enables flexible and loosely coupled code. In this section, we’ll explore how to use dependency injection for implementing the Composite Pattern.

Autofac

 The code with dependency injection using Autofac in C# is available on GitHub here

C#

To use Autofac for dependency injection in C#, we can create a ContainerBuilder and register each concrete type of the IComponent interface. Then, we build the IContainer object and use a ComponentFactory to create the various IComponent types in the client. Notice that the new keyword is removed from the client, which helps to decouple it from the IComponent concrete types.

static IContainer container;

public static void Main()
{
   // Register dependencies.
   var builder = new ContainerBuilder();

   builder.RegisterType<Directory>().Keyed<IComponent>("directory");
   builder.RegisterType<File>().Keyed<IComponent>("file");
   builder.RegisterType<ComponentFactory>().AsSelf();

   container = builder.Build();

   RunExample();
}

private static void RunExample()
{
   // Use the factory to create the dependencies.
   var componentFactory = container.Resolve<ComponentFactory>();
   var root = componentFactory.Create("directory", new { name = "root" });
   var subDir1 = componentFactory.Create("directory", new { name = "subdir1" });
   var subDir2 = componentFactory.Create("directory", new { name = "subdir2" });
   var file1 = componentFactory.Create("directory", new { name = "file1", data = new byte[] { 0x33, 0x34 } });
   var file2 = componentFactory.Create("directory", new { name = "file2", data = new byte[] { 0x33, 0x34, 0x35 } });

   root.Add(subDir1);
   root.Add(subDir2);
   subDir1.Add(file1);
   subDir2.Add(file2);
   
   // the rest of the example is the same shown in previous section.
}

The ComponentFactory class is shown below:

public class ComponentFactory
{

   // Use the ILifetimeScope to resolve dependencies using Autofac.
   private readonly ILifetimeScope _lifeTimeScope;

   public ComponentFactory(ILifetimeScope lifeTimeScope)
   {
      _lifeTimeScope = lifeTimeScope;
   }

   public IComponent Create(string key, object _params)
   {
      // For more clarity in the callers we use anonymous types in C# for the _params,
      // which we convert them to list of NamedParameters
      var props = _params.GetType().GetProperties();
      var namedParameters = props.Select(x => new NamedParameter(x.Name, x.GetValue(_params, null)));
      return _lifeTimeScope.ResolveKeyed<IComponent>(key, namedParameters);
   }
}

Using dependency injection with Autofac in C# is a powerful way to make your code more flexible and decoupled. By registering your components to a dependency injection container, you can create instances of your components in a more flexible way and with less coupling to the concrete implementation of these classes.

Recommendations

Looking to dive deeper into design patterns and take your programming skills to the next level? Check out the following highly recommended books:

clean code cover

Clean Code

Author: Robert C. Martin
Publication: August 1, 2008

head first design patterns

Head First – Design Patterns

Authors: Eric Freeman, Elisabeth Robson
Publication: January 12, 2021

* This site contains product affiliate links. We may receive a commission if you make a purchase after clicking on one of these links.

Write A Comment

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.