Builder and Composite design patterns C#

Sukitha Jayasinghe
Mar 10 · 5 min read

The composite design pattern is a common structural design pattern that composes objects in a tree structure and provides an interface to work with them as they were individual objects. This pattern creates a tree structure with objects inherited from the same base and provides a nice abstraction over the structure.

Apply this design pattern when:

  • Require generic function on top of hierarchical structure
  • Require to treat complex and individual objects uniformly

Purpose:

  • Adding new elements to the structure without breaking the code preserves the Open-Closed Principle

Real word examples:

Popular use case of this pattern is Directory/Folder structure, HTML tags. JSON structure, YAML structure and XML. The .net XElement class is implemented using the composite pattern.

XContainer document from official Microsoft site

XContainer abstract class can be considered as the base, both XDocument and XElements inherited from it. XDocument and XElement both can be considered as composites. You can read more from the official site

Example:

In this example, a lighter version of XElement is implemented for demonstration purposes.

Class diagram:

Composite design

IXElement.cs

This is the composite interface that describes common methods such as Add() and Remove() for XML document and XML Elements.

using System;
using System.Collections.Generic;
using System.Text;
namespace Composite
{
public interface IXElement
{
string XName { get; }
void Add(IXElement element);
void Remove(IXElement element);
List<IXElement> Elements();
}
}

XDocument.cs

Implementation of the XML document, This has an additional property to keep a comment on. This class has two constructors, one is with comment and the other one with comment and list of parameters, which supports passing a list of XML elements on creation.

using System;
using System.Collections.Generic;
using System.Text;
namespace Composite
{
public class XDocument : IXElement
{
private readonly List<IXElement> children;
public string XName { get; }
public string Comment { get; }
public XDocument(string _comment, params IXElement[] items)
{
children = new List<IXElement>();
Comment = _comment;
foreach (var _item in items) {
children.Add(_item);
}
}
public XDocument(string _comment)
{
children = new List<IXElement>();
Comment = _comment;
}
public void Add(IXElement element)
{
children.Add(element);
}
public void Remove(IXElement element)
{
children.Remove(element);
}
public override string ToString()
{
var document = new StringBuilder().Append($"< !--{Comment}-->");
document.Append("<Root>");
foreach (var item in children) {
document.Append(item.ToString());
}
document.Append(@"<\Root>");
return document.ToString();
}
public List<IXElement> Elements()
{
return children;
}
}
}

XElement.cs

This is the implementation of the XML element. This class has three constructors with a combination of name, value, and element parameter list. This provides the caller with the flexibility of creating XML elements with and without inner elements.

using System;
using System.Collections.Generic;
using System.Text;
namespace Composite
{
public class XElement : IXElement
{
public string XValue { get; }
private readonly List<IXElement> children; public string XName { get; } public XElement(string name)
{
XName = name;
children = new List<IXElement>();
}
public XElement(string name, string value)
{
XName = name;
XValue = value;
children = new List<IXElement>();
}
public XElement(string name, params IXElement[] items)
{
children = new List<IXElement>();
XName = name;
foreach (var _item in items)
{
children.Add(_item);
}
}
public void Add(IXElement element)
{
children.Add(element);
}
public void Remove(IXElement element)
{
children.Remove(element);
}
public override string ToString()
{
var document = new StringBuilder();
document.Append($"<{XName}>");
if (children.Count > 0)
{
foreach (var item in children)
{
document.Append(item.ToString());
}
}
else {
if(XValue != null)
document.Append(XValue.ToString());
}
document.Append(@"<\"+XName+">");
return document.ToString();
}
public List<IXElement> Elements()
{
return children;
}
}
}

main.cs

In the main method, XML Document is created by passing the comment and list of XML elements to the constructor. Observe the ability to pass the hierarchy of elements in the constructor.

using Composite;
using System;
namespace Main
{
class Program
{
static void Main(string[] args)
{
XDocument document = new XDocument("This is the root element",
new XElement("child1",
new XElement("child1.1",
new XElement("child1.1.1", "1.1.1"))),
new XElement("child2", "2"),
new XElement("child3", "3"),
new XElement("child4", "4"));
var element5 = new XElement("child4", "5");
document.Add(element5);
Console.WriteLine(document);
document.Remove(element5);
Console.WriteLine(document);
}
}
}

Relationship with other patterns:

In the real world, patterns don’t exist individually but are woven together to provide solutions to complex design problems.

  • Builder: Can use when creating complex tree structures
  • Iterator: Iterates through the complex structure
  • Flyweight: Save memory resources when creating objects

The above example extends with a builder to demonstrate how the pattern works together. The XML construction above seems a bit messy so a fluent builder is added to simplify the XML creation process.

Previous article about the Builder pattern.

Class Diagram with Builder

class diagram with the builder

IXMLBuilder.cs

This interface provides methods to add elements and reset the structure.

namespace Builder
{
public interface IXMLBuilder
{
public XDocument Root { get; }
IXMLBuilder AddElement(string name, string value);
IXMLBuilder AddElement(string name);
IXMLBuilder MoveTo(string name);
IXMLBuilder Reset();
}
}

XMLBuilder.cs

Implementation of builder interface which has the root document and simple logic to add elements.

using Composite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Builder
{
public class XMLBuilder : IXMLBuilder
{
public XDocument Root { get; }
internal IXElement Current { get; set; }
public XMLBuilder(string comment) {
Root = new XDocument(comment);
Current = Root;
}
public IXMLBuilder AddElement( string name, string value)
{
Current.Add(new XElement(name, value));
return this;
}
public IXMLBuilder MoveTo(string name)
{
List<IXElement> filtered = Current.Elements().Where(x => x.XName == name).ToList();
Current = filtered.Count > 0 ? filtered[0] : Root;
return this;
}
public IXMLBuilder Reset()
{
Current = Root;
return this;
}
public IXMLBuilder AddElement(string name)
{
var temp = new XElement(name);
Current.Add(temp);
Current = temp;
return this;
}
}
}

main.cs

A simpler way of building the XML tree using the builder interface.

using Builder;
using Composite;
using System;
namespace Main
{
class Program
{
static void Main(string[] args)
{
XDocument xdocument = new XMLBuilder("This is the root element")
.AddElement("child1")
.AddElement("child1.1")
.AddElement("child1.1.1", "1.1.1")
.Reset()
.AddElement("child2", "2")
.AddElement("child2", "3")
.AddElement("child2", "4")
.AddElement("child2", "5")
.Root;
Console.WriteLine(xdocument);
}
}
}

Discussion:

Some argue that the original pattern is violating the Liskov substitutional principle and Interface segregation principle by forcing leaf nodes to implement meaningless methods such as Add() or Remove(). However, The sample provided in this article complies with ISP and LSP, as XElement and XDocument has implemented all abstract methods.

Thank you, Desmond Harris Fernando and Dinusha Kannangara for reviewing the code.

Source code available in git,

Nerd For Tech

From Confusion to Clarification

Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/.

Sukitha Jayasinghe

Written by

Software Architect with 10+ years experience designing and developing enterprise and cloud applications

Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To know more about us, visit https://www.nerdfortech.org/.