The Composite design pattern is a structural design pattern that allows us to compose objects into a tree structure and then work with that structure as if it is a single object. That also means using this design pattern makes sense when the part of our app can be represented as a tree.
In this article, we are going to learn how to implement the Composite Design Pattern into our project and what are its benefits.
The source code is available at the Composite Design Pattern – Source Code.
For the main page of this series check out C# Design Patterns.
Composite Design Pattern Structure
The Composite design pattern consists of the following parts:
- Component
- Leaf
- Composite
A component is an interface that describes operations that are common to either simple or complex elements of the tree.
A leaf is a single object, that doesn’t have sub-elements. Our tree structure consists of more leaf objects.
A Composite is an object that does have sub-elements (leaves or other composite objects). Interesting thing is that the Composite object isn’t familiar with the concrete classes of its children. It communicates with its children via the Component interface.
Finally, we have a client, which works with all the elements through the Component interface.
But enough of the theory, let’s start with the concrete example.
Composite Design Pattern Implementation
Let’s imagine that we need to calculate the total price of a gift which we are selling in our shop. The gift could be a single element (toy) or it can be a complex gift that consists of a box with two toys and another box with maybe one toy and the box with a single toy inside. As we can see, we have a tree structure representing our complex gift so, implementing the Composite design pattern will be the right solution for us.
So, let’s start with the Component
part:
public abstract class GiftBase { protected string name; protected int price; public GiftBase(string name, int price) { this.name = name; this.price = price; } public abstract int CalculateTotalPrice(); }
We can see that our component consists of two protected fields and one abstract method. These fields and method are going to be used as an interface between the Leaf and the Composite part of our pattern.
Now, in many examples, we can see additional operations like add and remove inside the abstract class, but we are not going to add them in this class, because our Leaf class doesn’t need them. What we are going to create instead is a new interface – IGiftOperations
:
public interface IGiftOperations { void Add(GiftBase gift); void Remove(GiftBase gift); }
Only our composite object will implement this interface, but the leaf object won’t. This is much better because our leaf object doesn’t need to implement the methods it won’t use.
The Composite Class Implementation
So, let’s continue on with the Composite
class:
public class CompositeGift : GiftBase, IGiftOperations { private List<GiftBase> _gifts; public CompositeGift(string name, int price) :base(name, price) { _gifts = new List<GiftBase>(); } public void Add(GiftBase gift) { _gifts.Add(gift); } public void Remove(GiftBase gift) { _gifts.Remove(gift); } public override int CalculateTotalPrice() { int total = 0; Console.WriteLine($"{name} contains the following products with prices:"); foreach (var gift in _gifts) { total += gift.CalculateTotalPrice(); } return total; } }
The class implementation is pretty straightforward. First, we have the GiftBase
type list in which we store our Leaf or other Composite objects. We can add or remove those objects from our list by implementing Add
and Remove
methods from our IGiftOperations
interface. Finally, we are calculating the total price of our Gift
object with all the sub-gifts inside it.
The Leaf Class Implementation
Let’s continue on with the leaf part:
public class SingleGift : GiftBase { public SingleGift(string name, int price) :base(name, price) { } public override int CalculateTotalPrice() { Console.WriteLine($"{name} with the price {price}"); return price; } }
And that is all we need for the Leaf
implementation because it doesn’t have a sub-levels so it doesn’t require to add or remove features at all.
Finally, we can implement our client part:
class Program { static void Main(string[] args) { var phone = new SingleGift("Phone", 256); phone.CalculateTotalPrice(); Console.WriteLine(); //composite gift var rootBox = new CompositeGift("RootBox", 0); var truckToy = new SingleGift("TruckToy", 289); var plainToy = new SingleGift("PlainToy", 587); rootBox.Add(truckToy); rootBox.Add(plainToy); var childBox = new CompositeGift("ChildBox", 0); var soldierToy = new SingleGift("SoldierToy", 200); childBox.Add(soldierToy); rootBox.Add(childBox); Console.WriteLine($"Total price of this composite present is: {rootBox.CalculateTotalPrice()}"); } }
We can see, that we are creating one gift with just one element inside it and one complex gift with the toys and an additional box with a single toy inside.
So, as soon as we run our app, we are going to get this result:
Excellent. That wraps it up for the Composite design pattern.
Conclusion
Even though this pattern might look a bit complex, it is very beneficial to use it when we have complex tree structures in our code.
Furthermore, by having a single interface that is shared by all the elements in the Composite pattern, the client doesn’t have to worry about the concrete class it works with.