Composite And Builder Design Pattern With A Tree

Introduction 

 
The composite pattern provides a way to work with tree structures. In other words, data structures with parent/child relationships. For example, JSON, HTML, XML. In the .NET framework, XElement is developed using a composite design pattern. You know it's important when the creator is doing it.
 
A real-world example would be the file system.
 
Composite And Builder Design Pattern With A Tree
 
As you can see, the directory can contain multiple directories or files in it. However, files can not contain more files or directories as they are considered as leaf nodes. The composite pattern enables clients to interact with individual objects or compositions of objects in a uniform fashion. (Composition: has - a relationship) Now the composite pattern is not only about modelling tree structures, but in particular, enabling clients to act on those tree structures uniformly. Let's understand what does that means by our file structure example. If I want to get the size of an individual file, then I could have a method on this individual file to give me its size, and it returns me the number of bytes in the file. But what if I wanted the size of a directory, say Directory Blog? I just have to call a method on Directory Blog and as a consumer, I don't want to have to worry about what's going on through internal logic to get the total size of the directory Blog.
 
Conceptually, this is what the composite pattern is enabling, whether I want to act upon the granular level or an individual leaf node. The composite pattern will enable us to deal with all this uniformly. Take a look at the conceptual diagram.
 

 
 
Let's go ahead into code and develop one .NET core console application for our file structure example. We will try to get the size of a file or a directory.
 
First, add a component class 
  1. namespace CompositeDesignPattern.FileSystem  
  2. {  
  3.     public abstract class FileSystemItem  
  4.     {  
  5.         #region Properties  
  6.         public string Name { get; }  
  7.         #endregion  
  8.  
  9.         #region Constructor  
  10.         public FileSystemItem(string name)  
  11.         {  
  12.             this.Name = name;  
  13.         }  
  14.         #endregion  
  15.  
  16.         #region Methods  
  17.         public abstract decimal GetSizeinKB();  
  18.         #endregion  
  19.     }  

Then, we need to take care of the leaf node.
  1. namespace CompositeDesignPattern.FileSystem  
  2. {  
  3.     public class FileItem : FileSystemItem  
  4.     {  
  5.         #region Properties  
  6.         public long FileBytes { get; }  
  7.         #endregion  
  8.  
  9.         #region COnstructor  
  10.         public FileItem(string name, long fileBytes) : base(name)  
  11.         {  
  12.             this.FileBytes = fileBytes;  
  13.         }  
  14.  
  15.         #endregion  
  16.         public override decimal GetSizeinKB()  
  17.         {  
  18.             return decimal.Divide(this.FileBytes , 1000);  
  19.         }  
  20.     }  
  21. }  
At last, let's go ahead and add the composite class.
  1. using System.Collections.Generic;  
  2. using System.Linq;  
  3.   
  4. namespace CompositeDesignPattern.FileSystem  
  5. {  
  6.     class Directory : FileSystemItem  
  7.     {  
  8.         #region Properties  
  9.          
  10.         /// <summary>  
  11.         /// Here we are using 2 of c#-6's festures   
  12.         /// Readonly properties & auto property initializer  
  13.         /// </summary>  
  14.         public List<FileSystemItem> childrens { get; } = new List<FileSystemItem>();  
  15.         #endregion  
  16.  
  17.         #region COnstructor  
  18.         public Directory(string name) : base(name)  
  19.         {  
  20.               
  21.         }  
  22.         #endregion  
  23.  
  24.         #region Methods  
  25.   
  26.         public override decimal GetSizeinKB()  
  27.         { 
  28.             // Summarizing size of children
  29.             return this.childrens.Sum(x => x.GetSizeinKB());  
  30.         }  
  31.   
  32.         public void Add(FileSystemItem newNode)  
  33.         {  
  34.             this.childrens.Add(newNode);  
  35.         }  
  36.         public void Remove(FileSystemItem deleteNode)  
  37.         {  
  38.             this.childrens.Remove(deleteNode);  
  39.         }  
  40.         #endregion  
  41.     }  

Finally, our caller class:
  1. using CompositeDesignPattern.FileSystem;  
  2. using CompositeDesignPattern.Structural;  
  3. using System;  
  4.   
  5. namespace CompositeDesignPattern  
  6. {  
  7.     class Program  
  8.     {  
  9.         //Client  
  10.         static void Main(string[] args)  
  11.         {  
  12.             //let's create a Directory  
  13.             var root = new Directory("Root");  
  14.   
  15.             // Add 2 more folders  
  16.             var folder1 = new Directory("Folder1");  
  17.             var folder2 = new Directory("Folder2");  
  18.   
  19.             // Add them under root directory  
  20.             root.Add(folder1);  
  21.             root.Add(folder2);  
  22.   
  23.             //Add files to folder 1   
  24.             folder1.Add(new FileItem("MyBook.txt", 12000));  
  25.             folder1.Add(new FileItem("MyVideo.mkv", 1000000));  
  26.   
  27.             //Add sub directory to folder 1   
  28.             var subfolder1 = new Directory("Sub Folder1");  
  29.             subfolder1.Add(new FileItem("MyMusic.mp3", 20000));  
  30.             subfolder1.Add(new FileItem("MyResume.pdf", 18000));  
  31.             folder1.Add(subfolder1);  
  32.   
  33.             //Add files to folder 2  
  34.             folder2.Add(new FileItem("AndroidApp.apk", 250000));  
  35.             folder2.Add(new FileItem("WPFApp.exe", 87000000));  
  36.   
  37.             Console.WriteLine($"Total size of (root): { root.GetSizeinKB() } KB");  
  38.             Console.WriteLine($"Total size of (folder 1): { folder1.GetSizeinKB() }KB");  
  39.             Console.WriteLine($"Total size of (folder 2): { folder2.GetSizeinKB() }KB");  
  40.         }   
  41.     }  

As you can see, the caller only needs to call GetSizeinKB() function to get the size of any directory or a file.
 
Now run your program and check the output... It works perfectly.
 
Composite And Builder Design Pattern With A Tree
 
Everything works well, except that the caller class is pretty messed up. We are adding up files without a proper order, plus every time we have to initialize a directory or file, we have to use a new keyword, which looks pretty messy.
 
What we can do is we can add a builder class in the middle. Here's how you can design a builder pattern:
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4.   
  5. namespace CompositeDesignPattern.FileSystem  
  6. {  
  7.     public class FileSystemBuilder  
  8.     {  
  9.         internal Directory Root { get; }  
  10.         internal Directory CurrentDirectory { getset; }  
  11.         #region Properties  
  12.         #endregion  
  13.  
  14.         #region Constructor  
  15.         public FileSystemBuilder(string rootDirectory)  
  16.         {  
  17.             // Notice we've already encapsulated the instantiation of the object.  
  18.             this.Root = new Directory(rootDirectory);  
  19.             this.CurrentDirectory = Root;  
  20.         }  
  21.   
  22.         internal FileItem AddFile(string name, int size)  
  23.         {  
  24.             var file = new FileItem(name, size);  
  25.             CurrentDirectory.Add(file);  
  26.             return file;  
  27.         }  
  28.   
  29.         internal Directory SetCurrentDirectory(string currentDirName)  
  30.         {  
  31.             var dirStack = new Stack<Directory>();  
  32.             dirStack.Push(this.Root);  
  33.             while (dirStack.Any())  
  34.             {  
  35.                 //topmost element  
  36.                 var currentDir = dirStack.Pop();  
  37.   
  38.                 if(currentDirName == currentDir.Name)  
  39.                 {  
  40.                     this.CurrentDirectory = currentDir;  
  41.                     return currentDir;  
  42.                 }  
  43.   
  44.                 //iterate through childrens  
  45.                 //As directory can have file and directory both we have specify what are we iterating  
  46.                 foreach (var item in currentDir.childrens.OfType<Directory>())  
  47.                 {  
  48.                     //we are inserting childrens of the root and it will check for its name in code up above  
  49.                     dirStack.Push(item);  
  50.                 }  
  51.             }  
  52.             //let the user know that he is looking for wring directory.  
  53.             throw new InvalidOperationException($"Directory name: '{ currentDirName }' not found!");  
  54.         }  
  55.   
  56.         internal Directory AddDirectory(string name)  
  57.         {  
  58.             var dir = new Directory(name);  
  59.             this.CurrentDirectory.Add(dir);  
  60.             this.CurrentDirectory = dir;  
  61.             return dir;  
  62.         }  
  63.         #endregion  
  64.     }  

As you can see, we have encapsulated composite within our Builder class CompositeDesignPattern.

Also, notice that in our method SetCurrentDirectory(). We are not performing a recursive to search a directory, as it is not an optimal solution if the directory is large enough. Rather, we have used an iterative stack-based solution.
 
Now see how our calling class looks:
  1. using CompositeDesignPattern.FileSystem;  
  2. using CompositeDesignPattern.Structural;  
  3. using Newtonsoft.Json;  
  4. using System;  
  5.   
  6. namespace CompositeDesignPattern  
  7. {  
  8.     class Program  
  9.     {  
  10.         //Client  
  11.         static void Main(string[] args)  
  12.         {  
  13.             //Much more redable code.  
  14.             var builder = new FileSystemBuilder("root");  
  15.             builder.AddDirectory("Folder1");  
  16.             builder.AddFile("MyBook.txt", 12000);  
  17.             builder.AddFile("MyVideo.mkv", 1000000);  
  18.             builder.AddDirectory("SubFolder");  
  19.             builder.AddFile("MyMusic.mp3", 20000);  
  20.             builder.AddFile("MyResume.pdf", 18000);  
  21.             builder.SetCurrentDirectory("root");  
  22.             builder.AddDirectory("Folder1");  
  23.             builder.AddFile("AndroidApp.apk", 250000);  
  24.             builder.AddFile("WPFApp.exe", 87000000);  
  25.   
  26.             Console.WriteLine($"Total size of (root): { builder.Root.GetSizeinKB() } KB");  
  27.             //lets show it in a json format  
  28.             Console.WriteLine(JsonConvert.SerializeObject(builder.Root, Formatting.Indented));             
  29.         }   
  30.     }  

We will display the object in JSON format to see it's tree-like structure.
 
Now when you run this app, you'll get the following output:
 
Composite And Builder Design Pattern With A Tree
 
Now that's how you roll. Understand conceptually first, then it will easy to implement.
 
I sincerely hope you enjoyed this blog and that you're inspired to apply what you've learned to your own applications. Thank you.
 
Connect with me:


Similar Articles