Introduction
The aim of this article is to understand the basics of the Composite pattern and try to see when it can be useful. We will also look at a toy application using composite pattern to get better understanding of the pattern. The example code in this article will be mainly for the illustration of this pattern and will be a not-so-real world example. The idea of this this article is to illustrate the concept of composite pattern only.
Background
Many a times in our applications we encounter a scenario where we need to have a tree type data structure where we need to handle collections of collections in a seamless manner.If we have a scenario where we have a nested collections i.e. a tree data structure and the calling code need to handle this data structure in such a way that there should be no difference in handling an individual item that is contained inside the tree i.e. single item or a tree node containing multiple items i.e. a composite.
One classic example that we can think of for this is our UI controls. Every control is a UIControl
be it form or be it a button. But a form could contain other controls like buttons and text boxes. So the form is a composite control(of type UIControl
) that contains a lot of other controls(of type UIControl
). if we drag a Panel control on form that Panel control itself could contain other controls and forms now contains panel control. So all of these objects are of type UIControl
but few of them exist independently like button and few actually contain other controls. But from the Rendering and event handling perspective all these controls are treated same. That would mean that code for rendering and event handling is treating the composite objects i.e. form containing other controls and the button controls all the same.
In short, Composite patterns should be used whenever we want the calling code(clients) to treat the compositions of objects and individual objects in the same manner.
Before getting further to understanding this pattern using example, lets look at the Gang of Four definition and class diagram for this pattern.
"Compose objects into tree structure to represent part-whole hierarchies.Composite lets client treat individual objects and compositions of objects uniformly"
Few things that I want to explicitly call out(from OO perspective) before understanding what each component in this diagram is.
- A
Composite
is a Component
. - A
Leaf
is a Component
. - A
Composite
has Components
.
Let us try to understand each component of this class diagram.
Component
: This is the contract/interface for the objects in the composition. In some cases this could also implements default behavior common to all derived classes i.e. Composite and Leaf Leaf
: This is the object that has no objects of Component type inside this i.e. no children. Composite
: This class contains other Component as children and defines contract/interface required for all Composite types down the hierarchy. Client
: Used the Leaf and Composite type objects in exact same way.
Using the code
To understand this better let's try to implement a simple application where we will see how this pattern can help us manage nested collections and data in tree structure in a seamless manner.
Note: The focus of this example will be to illustrate the composite pattern and how using this makes the calling code in client cleaner. There are some shortcuts taken on code quality and some design choices. Please ignore that part as the idea is only to focus on the pattern. I will make a note of these things as and when we see the code as improvement note.
So to illustrate this pattern lets take a problem where we have an organization and this organization let the employees subscribe to various types of training subscriptions. The small module that we will be creating is to find the cost implication on the organization due to this for a given employee. If we look at a typical organization, there will be employees and there will be managers. The managers also have managers. So an organization structure is of a tree type.
The client will try to retrieve the subscription cost for an employee. If the Employee is not a manager, the cost will be for only his subscriptions but if he is a manager, the cost will be for his subscriptions and the cost of subscription for his team members. Let;s try to work on this problem statement and see how we can leverage composite pattern to design the solution in such a way that client can retrieve the cost of subscriptions in same way irrespective of the employee being a manager or not.
Let's visualize the proposed solution before looking at the code:
Let's start by creating a simple enum SubscriptionType
and a class Subscription
that will track the type of the subscription and a class to store the subscription details.
enum SubscriptionType
{
Print,
Portal,
Training
}
class Subscription
{
public SubscriptionType SubscriptionType { get; set; }
public string Name { get; set; }
public float Cost { get; set; }
}
Improvement Note: This is not the best way to design this class but we are keeping the code simple just to focus on the parts that are relevant for this pattern. For now this class will just help us encapsulate the subscription information.
Now that we have the subscription class ready, lets try to create the Component
part of the pattern. For this, let's create an interface of type IEmployee
.
interface IEmployee
{
string Name { get; set; }
int EmployeeId { get; set; }
List<subscription> Subscriptions { get; set; }
float GetCost();
int GetSubscriptionCount(SubscriptionType type);
}
This interface provides a contract that all Leaf
and Composite
types should implement. It keeps track of Employee
specific attributes and list of subscriptions. It also has the methods to retrieve the subscription information for a given employee.
Now let's look at our Leaf class i.e. Employee
.
class Employee : IEmployee
{
public string Name { get; set; }
public int EmployeeId { get; set; }
public List<subscription> Subscriptions { get; set; }
public float GetCost()
{
if (Subscriptions == null)
{
return 0;
}
float cost = Subscriptions.Select((item => item.Cost)).Sum();
return cost;
}
public int GetSubscriptionCount(SubscriptionType type)
{
if (Subscriptions == null)
{
return 0;
}
int count = Subscriptions.Count(item => item.SubscriptionType == type);
return count;
}
}
Improvement Note: This class has all the same members that are in our IEmployee
interface. One way to implement this could have been to either have an abstract class instead of interface where the abstract class could provide the default implementation of the GetCost
and GetSubscriptionCount
methods.
Now that we have the Employee
class i.e. the Leaf
node ready, let's see how we can create the Manager
class.
class Manager : IEmployee
{
public string Name { get; set; }
public int EmployeeId { get; set; }
public List<subscription> Subscriptions { get; set; }
public List<iemployee> TeamMembers { get; set; }
public float GetCost()
{
float subsCost = 0;
if (Subscriptions != null)
{
subsCost = Subscriptions.Select((item => item.Cost)).Sum();
}
float membersCost = 0;
if (TeamMembers != null)
{
membersCost = TeamMembers.Select(item => item.GetCost()).Sum();
}
return subsCost + membersCost;
}
public int GetSubscriptionCount(SubscriptionType type)
{
int subCount = 0;
if (Subscriptions != null)
{
subCount = Subscriptions.Count(item => item.SubscriptionType == type);
}
int membersSubCount = 0;
if (TeamMembers != null)
{
membersSubCount = TeamMembers.Select(item => item.GetSubscriptionCount(type)).Sum();
}
return subCount + membersSubCount;
}
}
If we look at the Manager
class, we can see that it is a composite containing a collection of IEmployees
. Also, the GetCost
and GetSubscriptionCount
count method are overriden in this class to handle the cost calculations based on the Manager's
own subscriptions and his team member Employee's
subscriptions.
The beauty of this code is that the caller will use the same logic to get the cost of the subscription from Manager
and Employee
object. The handling of the sub nodes inside the composite is totally encapsulated inside the Composite
object itself.
Improvement Note: The code shown here is just to illustrate the composite pattern only as we can clearly see that there is a "is-a
" relationship between Manager and Employee. So why are we modeling them as separate classes. it is just to illustrate the pattern as is. In ideal case, we should have the Employee
as base class of Manager
. Or better, single Employee class can be used and we can use state pattern to identify if the object at the moment is Employee
or Manager
.
Now that we have the classes ready to be consumed. Let's look at the client code on how it is consuming the classes to calculate cost.
Note: The client code is rather messy and lengthy just because the setup code that is required to create subscription and employee classes. First let me post the complete client code and then I will show how the same logic is being used to display cost information irrespective of the object being of Employee
or Manager
type.
class Program
{
static void Main(string[] args)
{
IEmployee emp1 = new Employee { Name = "A", EmployeeId = 1, Subscriptions = new List<subscription> { GetPluralSightSubscription(), GetLyndaSubscription(), GetMSDNSubscription(), GetTrainingSubscription() } };
IEmployee emp2 = new Employee { Name = "B", EmployeeId = 2, Subscriptions = new List<subscription> { GetPluralSightSubscription(), GetLyndaSubscription() } };
IEmployee emp3 = new Employee { Name = "C", EmployeeId = 3, Subscriptions = new List<subscription> { GetPluralSightSubscription() } };
IEmployee emp4 = new Employee { Name = "D", EmployeeId = 4, Subscriptions = new List<subscription> { GetPluralSightSubscription() } };
IEmployee emp5 = new Employee { Name = "E", EmployeeId = 5, Subscriptions = new List<subscription> { GetPluralSightSubscription() } };
IEmployee emp6 = new Employee { Name = "F", EmployeeId = 6, Subscriptions = new List<subscription> { GetPluralSightSubscription(), GetTrainingSubscription() } };
IEmployee emp7 = new Employee { Name = "G", EmployeeId = 7, Subscriptions = new List<subscription> { GetPluralSightSubscription() } };
IEmployee emp8 = new Employee { Name = "H", EmployeeId = 8, Subscriptions = new List<subscription> { GetPluralSightSubscription() } };
IEmployee emp9 = new Employee { Name = "I", EmployeeId = 9, Subscriptions = new List<subscription> { GetPluralSightSubscription() } };
IEmployee emp10 = new Employee { Name = "J", EmployeeId = 10, Subscriptions = new List<subscription> { GetPluralSightSubscription(), GetMSDNSubscription() } };
Console.WriteLine("Lets get cost details of single employee");
PrintCostDetails(emp1);
List<iemployee> employees = new List<iemployee> { emp1, emp2, emp3, emp4, emp5, emp6, emp7, emp8, emp9, emp10 };
Console.WriteLine("Lets check cost details of list of employees");
foreach (var item in employees)
{
PrintCostDetails(item);
}
IEmployee mng1 = new Manager
{
Name = "MA",
EmployeeId = 11,
Subscriptions = new List<subscription>
{
GetPluralSightSubscription(),
GetLyndaSubscription(),
GetMSDNSubscription(),
GetTrainingSubscription()
},
TeamMembers = new List<iemployee>
{
emp1,
emp2,
emp3,
}
};
IEmployee mng2 = new Manager
{
Name = "MB",
EmployeeId = 13,
Subscriptions = new List<subscription>
{
GetPluralSightSubscription(),
},
TeamMembers = new List<iemployee>
{
emp4,
emp5,
emp6,
}
};
IEmployee mng3 = new Manager
{
Name = "MC",
EmployeeId = 13,
Subscriptions = new List<subscription>
{
GetTrainingSubscription()
},
TeamMembers = new List<iemployee>
{
emp7,
emp8,
emp9,
emp10,
}
};
Console.WriteLine("Lets get cost details of single manager");
PrintCostDetails(mng1);
Console.WriteLine("Lets check cost details of list of manager");
foreach (var item in new List<iemployee>{mng1, mng2, mng3})
{
PrintCostDetails(item);
}
Console.ReadLine();
}
private static void PrintCostDetails(IEmployee item)
{
Console.WriteLine("Cost: {0}, Count Portal: {1}, Count Print: {2}, Count Training: {3}, Emp ID: {4}",
item.GetCost(),
item.GetSubscriptionCount(SubscriptionType.Portal),
item.GetSubscriptionCount(SubscriptionType.Print),
item.GetSubscriptionCount(SubscriptionType.Training),
item.EmployeeId);
}
#region Subscriptions gets
private static Subscription GetPluralSightSubscription()
{
Subscription sub = new Subscription
{
Name = "Pluralsight",
SubscriptionType = SubscriptionType.Portal,
Cost = 30
};
return sub;
}
private static Subscription GetLyndaSubscription()
{
Subscription sub = new Subscription
{
Name = "Lynda.com",
SubscriptionType = SubscriptionType.Portal,
Cost = 20
};
return sub;
}
private static Subscription GetMSDNSubscription()
{
Subscription sub = new Subscription
{
Name = "MSDN Magazine",
SubscriptionType = SubscriptionType.Print,
Cost = 10
};
return sub;
}
private static Subscription GetTrainingSubscription()
{
Subscription sub = new Subscription
{
Name = "Patterns Training",
SubscriptionType = SubscriptionType.Training,
Cost = 300
};
return sub;
}
#endregion
}
Most of the above shown code deals with setting up the objects and wiring up the objects to assign subscriptions to employees and creating employee manager relationship. But the code that is of our interest is encapsulated inside the function PrintCostDetails
.
private static void PrintCostDetails(IEmployee item)
{
Console.WriteLine("Cost: {0}, Count Portal: {1}, Count Print: {2}, Count Training: {3}, Emp ID: {4}",
item.GetCost(),
item.GetSubscriptionCount(SubscriptionType.Portal),
item.GetSubscriptionCount(SubscriptionType.Print),
item.GetSubscriptionCount(SubscriptionType.Training),
item.EmployeeId);
}
We will see how this code will remain the same and it will print the subscription cost of Employee
and Manager
seamlessly. Internally our Composite
class will handle all the business logic dealing with the composed objects. Let's run the code and see the output.
In this example we are only showing 1 level of hierarchy but the design is in place to handle N level of hierarchy too. The Composite
class and the Client
class need to change a bit and we will be able to handle any level of hierarchy with this code.
Point of Interest
In this article we have looked at the basics of composite pattern and how we can use this pattern to effectively manage the tree data that is in tree structure and nested collections. We have used a rather contrived example just to demonstrate this pattern. This article has been written from an absolute beginner’s perspective. I hope this has been informative.
History
- 21 July 2017: First version