Sie sind auf Seite 1von 10

An Absolute Beginner’s Tutorial for

Understanding and Implementing Composite


Pattern in C#
Rahul Rajat Singh, 8 Jul 2013

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.

1. A Composite is a Component.
2. A Leaf is a Component.
3. 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.

Hide Copy Code

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.

Hide Copy Code

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.

Hide Copy Code

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.

Hide Shrink Copy Code

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 Managerand 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.

Hide Shrink Copy Code

class Program
{
static void Main(string[] args)
{
// Now let us create some Employees and assign some subscriptions to them
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() } };

// lets get cost details of single employee


Console.WriteLine("Lets get cost details of single employee");
PrintCostDetails(emp1);

// Lets check cost details of list of employees


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);
}

// Now lets setup some managers


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,
}
};

// lets get cost details of single manager


Console.WriteLine("Lets get cost details of single manager");
PrintCostDetails(mng1);

// Lets check cost details of list of employees


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


// This region has cummy methods to add subscritions to employees
// This is just for demo, in real world applications should never have such code
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.

Hide Copy Code

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 Managerseamlessly. 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.

Das könnte Ihnen auch gefallen