Simplicity and Power of Composite Design Pattern
Last couple of days I was working on some small project where I had to deal with nested objects (or tree structure objects) and I immediately decided to use Composite design pattern as it is one of the best solution for dealing with tree structure objects. By nested objects or tree structure objects I mean the case when one object contains instance (or collection of instances) of its own type. For example XmlNode contains instances of more than one XmlNode (XmlNode.ChildNodes), another example is TreeNode, etc. Imagine you have to create your own tree structured custom class and to implement for example functionality of searching an object inside of the tree.
Composite pattern helps to implement such common functionalities as "Find an object from all tree structure", "Load all nested objects into the one collection", etc very easily. And I'll try to show on examples how to do that in easy (Composite) way.
First a little bit theory. Detail information about pattern of course you can fine on Gang of Four web site Composite Pattern .
In this UML diagram Component is an abstract class, Leaf is a class which is not include in the tree and is out of “composite” functionality and Composite is actually is our guy, the object which contain instances of its own type.
Let me simplify this graph a little bit and show it in other way.
As you see we do not have abstract (Component) class and Leaf and Client class is dealing with Composite class directly.
So, let’s start coding.
Let’s create two classes: first is Composite Class (we will call it MyClass) and its collection container (MyClassCollection).
MyClass will contain a constructor and two properties as Id (unique identification of class) and instance of MyClassCollection (collection of the same MyClass type belongs to object).
C# code looks like this:
public class MyClass
{
private int _id;
public int Id
{
get { return _id; }
}
private MyClassCollection _myClasses = new MyClassCollection();
public MyClassCollection MyClasses
{
get { return _myClasses; }
}
public MyClass(int id)
{
_id = id;
}
}
public class MyClassCollection : System.Collections.CollectionBase
{
public int Add(MyClass instance)
{
return List.Add(instance);
}
}
Lets now implement “Search object in a tree” functionality.
Lets create a method SearchMyClass inside of MyClass :
public MyClass SearchMyClass(int id)
{
MyClass mclass = null;
foreach (MyClass mc in MyClasses)
{
mclass = mc.SearchMyClass(id);
}
return mclass;
}
By calling SearchMyClass in a root object in a tree, method will iterate thru all instances of MyClasses collection and will call the same method in each of them, which means SearchMyClass method will be executed in each object in the tree. But what is the point of doing that, as you see it will return null at the end. Well, to get our searched object we have to add two condition statements into owr code:
public MyClass SearchMyClass(int id)
{
if (this.Id == id)
return this;
MyClass mclass = null;
foreach (MyClass mc in MyClasses)
{
mclass = mc.SearchMyClass(id);
if (mclass != null)
{
break;
}
}
return mclass;
}
Do you see magic like I see :). In the moment when method will find our object in the tree it will stop execution, return searched object and break a current foreach iteration. As it will return searched object intrations in all upper level will stop as well. In case if such object does not exist it will iterate thru all objects in the tree and return null. As simple as that.
What about to grab all MyClass objects from all tree into one collection. Let’s try that.
We will create new method called LoadMyClasses:
public void LoadMyClasses()
{
foreach (MyClass mc in MyClasses)
{
mc.LoadMyClasses();
}
}
As you see it is method again iterates thru collection of MyClasses and executes the same method for each of the instances. But where the magic ?
Look at the method now:
public void LoadMyClasses(ref MyClassCollection collection)
{
collection.Add(this);
foreach (MyClass mc in MyClasses)
{
mc.LoadMyClasses(ref collection);
}
}
First of all we have to specify the collection which we are planning to fill and the good approach by my opinion is to initialize an empty collection before calling the method and pass as reference with ref keyword which insures that passed object should be initialized before calling a method and inside of the method you are working directly with initialized object but not with the copy of its pointer.
Next, immediately during method execution we must add the object itself to the collection and then iterate thru its children. As you see all instances of the tree will be added to the collection. As simple as that again :)
The power of the Composite Design Pattern is get access to all objects in a tree by simple way and as you saw it is really simple. You can write various of methods which will search for specific data in tree, return some property value, etc, and by combining them you will get flexible way to deal with tree structure objects.
Have a fun.