A composite design pattern is a structural pattern in which we compose the whole-part relationship in a symmetric hierarchy. The client of the composite treats all the objects in the same fashion.
The whole concept of this design pattern lies in the fact that we can treat a composite object which consists of several other objects the same way as a leaf object. The client never knows that it is working on an object which has many other objects inside it.
Let us try to understand the composite pattern with an example. Here we have defined a class namely Shape, which is acting as the root of all the objects in the example. We have put all the basic functionalities of a graphical object in this class. These functions are Add (for adding a component), Remove (for removing a component), GetChild (for getting a pointer to a child), GetParentOfComponent (which will return the parent of a component), and Draw (to draw a component).
The class diagram of my solution looks like the following:
/*
* Shape.h
*
* Created on: 18-Apr-2015
* Author: som
*/
#ifndef SHAPE_H_
#define SHAPE_H_
#include
#include "LeafClassTypeException.h"
using namespace std;
class Shape
{
public:
Shape();
virtual ~Shape();
virtual void Add(unsigned int id)
{
throw LeafClassTypeException();
};
virtual void Remove(unsigned int id){};
//leaf classes will not override it..however, it will be overridden by the composite class.
virtual Shape* GetChild(unsigned int id)
{
throw LeafClassTypeException();
};
//Using this reference the "Chain of Responsibility" can be implemented
virtual Shape* GetParentOfComponent()
{
return ParentOfComponent;
};
virtual void SetParentOfComponent(Shape* s)
{
ParentOfComponent = s;
}
virtual void Display(){};
virtual Shape* FindItem(unsigned int id); //implementation afterwards
protected:
Shape* ParentOfComponent;
unsigned int resource_id;
};
#endif /* SHAPE_H_ */
/*
* Shape.cpp
*
* Created on: 18-Apr-2015
* Author: som
*/
#include "Shape.h"
#include
#include "global.h"
Shape::Shape() {
// TODO Auto-generated constructor stub
}
Shape::~Shape() {
// TODO Auto-generated destructor stub
}
Shape* Shape::FindItem(unsigned int id)
{
theIterator = Resource_Map.begin();
while (theIterator != Resource_Map.end())
{
theIterator = Resource_Map.find(id);
Shape* s = (*theIterator).second;
theIterator++;
return s;
}
return NULL;
}
Now from the Shape class, we have derived other graphic classes, like Point, Line and Rectangle. Among these classes, class Point is working as an helper class to define classes like Line and Rectangle. We have deduced another class called Picture which is composed of these component classes.
/*
* Point.h
*
* Created on: 18-Apr-2015
* Author: som
*/
#ifndef POINT_H_
#define POINT_H_
#include
#include "Shape.h"
#include "global.h"
using namespace std;
class Point : public Shape
{
public:
Point():x_Coord(0),y_Coord(0){}
Point(int x, int y):x_Coord(x), y_Coord(y){}
Point(const Point& p)
{
x_Coord = p.x_Coord;
y_Coord = p.y_Coord;
}
Point& operator = (const Point& p)
{
x_Coord = p.x_Coord;
y_Coord = p.y_Coord;
return *this;
}
virtual void Display()
{
cout<<"X Coordinate is:"<<x_Coord<<endl;
cout<<"Y Coordinate is:"<<y_Coord<<endl;
}
int X_COORD()
{
return x_Coord;
}
int Y_COORD()
{
return y_Coord;
}
virtual ~Point()
{
}
private:
int x_Coord;
int y_Coord;
};
#endif /* POINT_H_ */
/*
* Line.h
*
* Created on: 18-Apr-2015
* Author: som
*/
#ifndef LINE_H_
#define LINE_H_
#include
#include "Shape.h"
#include "global.h"
using namespace std;
//class Line is working as a leaf class.. Lets implement it as a final class
class Line : public Shape
{
private:
//private constructor
Line(unsigned int id):begin(0,0),end(0,0)
{
resource_id = id;
Resource_Map.insert(theMap::value_type(resource_id,(Shape*)this));
}
//private constructor
Line(unsigned int id, Point a, Point b):begin(a),end(b)
{
resource_id = id;
Resource_Map.insert(theMap::value_type(resource_id,(Shape*)this));
}
//private copy constructor
Line(const Line& in)
{
}
//private assignment operator
Line& operator=(const Line& in)
{
return *this;
}
public:
virtual void Display()
{
cout<<"Begining point is:";
begin.Display();
cout<<"End Point is:";
end.Display();
}
static Line* CreateLine(unsigned int id, Point a, Point b)
{
return new Line(id,a,b);
}
private:
virtual ~Line(){}
Point begin;
Point end;
};
#endif /* LINE_H_ */
/*
* Rectangle.h
*
* Created on: 18-Apr-2015
* Author: som
*/
#ifndef RECTANGLE_H_
#define RECTANGLE_H_
#include
#include "Shape.h"
#include "global.h"
using namespace std;
class Rectangle : public Shape
{
private:
//private constructor
Rectangle(unsigned int id, Point& p, int width, int height)
{
top_left = p;
top_right = Point(p.X_COORD() + width, p.Y_COORD());
bottom_left = Point(p.X_COORD() , p.Y_COORD() + height);
bottom_right = Point(p.X_COORD() + width, p.Y_COORD() + height);
resource_id = id;
Resource_Map.insert(theMap::value_type(resource_id,(Shape*)this));
}
//private copy constructor
Rectangle(const Rectangle& in)
{
}
//private assignment operator
Rectangle& operator=(const Rectangle& in)
{
return *this;
}
public:
static Rectangle* CreateRectange(unsigned int id, Point& p, int width, int height)
{
return new Rectangle(id, p, width, height);
}
virtual ~Rectangle(){}
virtual void Display()
{
cout<<"The four vertices are:"<<endl;
cout<<"Top Left :" ;
top_left.Display();
cout <<"Top Right :";
top_right.Display();
cout<<"Bottom Left :";
bottom_left.Display();
cout<<"Bottom Right :";
bottom_right.Display();
}
private:
//Attributes
Point top_left;
Point top_right;
Point bottom_left;
Point bottom_right;
};
#endif /* RECTANGLE_H_ */
The interesting point here is that, we have tried to implement the leaf classes, namely Line and Rectangle as final classes by making their constructors private and providing static member functions to create them.
The Picture class is composed of these leaf objects (Line and Rectangle).
/*
* Picture.h
*
* Created on: 18-Apr-2015
* Author: som
*/
#ifndef PICTURE_H_
#define PICTURE_H_
#include
#include
#include "Shape.h"
#include "global.h"
using namespace std;
class Picture : public Shape
{
public:
Picture(unsigned int id)
{
resource_id = id;
Resource_Map.insert(theMap::value_type(resource_id,(Shape*)this));
}
virtual void Display()
{
vector<Shape*>::iterator p = Components.begin();
while (p != Components.end())
{
(*p)->Display();
p++;
}
}
//Adds the component with the resource id equal to the passed parameter
virtual void Add (unsigned int id)
{
Shape* s = FindItem(id);
Components.push_back(s);
s->SetParentOfComponent(this);
}
//removes the component from the list with the resource_id equal
//to the parameter passed
virtual void Remove(unsigned int id)
{
Shape* s = FindItem(id);
vector<Shape*>::iterator p = Components.begin();
int pos = 0;
while (p != Components.end())
{
if(Components.at(pos) == s)
break;
pos++;
p++;
}
Components.erase(p);
s->SetParentOfComponent(NULL);
}
//will return the chile having the id equal to the passed value.
virtual Shape* GetChild (unsigned int id)
{
return FindItem(id);
}
virtual ~Picture()
{
vector<Shape*>::iterator p = Components.begin();
int pos = 0;
while (p != Components.end())
{
delete(Components.at(pos));
p++;
pos++;
}//while
Components.clear();
}
private:
vector<Shape*> Components;
};
#endif /* PICTURE_H_ */
The Shape class is called as the Component class, the Line and Rectangle classes are called the Leaf classes and the Picture class is called the Composite class.
Another interesting part of the example is that here every component is identifiable through its resource id. Whenever we create an object (leaf or composite object), it creates a key pair of the id and the pointer to that object and pushes this key into a MAP, from which we can easily search for that component in later times through its resource id.
There are many issues to consider when implementing the composite pattern.
We have defined one function called GetParentOfComponent. This can be useful to traverse the whole hierarchy of parent-child relationship. We have to make sure that any child can have only a composite object as its parent. We have ensured it by defining an Exception class which will be thrown the moment we want to add a component to a leaf object. The exception class can be defined as follows:
/*
* LeafClassTypeException.h
*
* Created on: 18-Apr-2015
* Author: som
*/
#ifndef LEAFCLASSTYPEEXCEPTION_H_
#define LEAFCLASSTYPEEXCEPTION_H_
#include
using namespace std;
class LeafClassTypeException
{
public:
void printerrormsg()
{
cout<<"This is a leaf class"<<endl;
}
};
#endif /* LEAFCLASSTYPEEXCEPTION_H_ */
The global data has been defined inside global.h & global.cpp files.
/*
* global.h
*
* Created on: 18-Apr-2015
* Author: som
*/
#ifndef GLOBAL_H_
#define GLOBAL_H_
#include
#include "Shape.h"
using namespace std;
typedef map <unsigned int, Shape*, less<unsigned int> > theMap;
extern theMap Resource_Map;
extern theMap::iterator theIterator;
#endif /* GLOBAL_H_ */
/*
* global.cpp
*
* Created on: 18-Apr-2015
* Author: som
*/
#include "global.h"
theMap Resource_Map;
theMap::iterator theIterator;
The client class looks like the following:
/*
* main.cpp
*
* Created on: 18-Apr-2015
* Author: som
*/
#include
#include "LeafClassTypeException.h"
#include "Point.h"
#include "Line.h"
#include "Rectangle.h"
#include "Picture.h"
using namespace std;
const int ID_LINE1 = 1;
const int ID_LINE2 = 2;
const int ID_LINE3 = 3;
const int ID_RECTANGLE1 = 4;
const int ID_PICTURE = 5;
int main()
{
Point p1(10,20);
Point p2(30,40);
Point p3(50,60);
Point p4(70,80);
Point p5(100,110);
Point p6(150,200);
Line* l1 = Line::CreateLine(ID_LINE1,p1,p2);
try
{
l1->Add(0);
}
catch(LeafClassTypeException& e)
{
e.printerrormsg();
}
Line* l2 = Line::CreateLine(ID_LINE2,p3,p4);
Line* l3 = Line::CreateLine(ID_LINE3,p5,p6);
Rectangle* r1 = Rectangle::CreateRectange(ID_RECTANGLE1, p1, 50,25);
Shape* p = new Picture(ID_PICTURE);
p->Add(ID_LINE1);
p->Add(ID_LINE2);
p->Add(ID_LINE3);
p->Add(ID_RECTANGLE1);
(p->GetChild(ID_RECTANGLE1))->Display();
p->Remove(ID_RECTANGLE1);
p->Display();
cout<<p<<endl;
cout<<l1->GetParentOfComponent()<<endl;
delete p;
return 0;
}
It should be noted that the functions like Add and Remove have been defined in the root class. Although for leaf classes, it does not do any meaningful things except throwing an exception, but it gives us transparency, because we can treat all components uniformly.
If we define these functions at the composite class level, then it would give the safety, because any attempt to Add or Remove from the leaf classes would give compile time error, but we would loose the transparency. The composite and the leaf classes will have different interfaces in this case.
The main participants in this design pattern are
Component (Shape) : It basically works as an abstract class which provides a common interface which will be used by the client to treat different classes uniformly. The common functionalities (e.g. Display) have been defined here. Other functionalities like Add, Remove, etc have been put in this class to maximize the role of this interface. The default behavior for Add and Remove has been implemented in such a fashion that for a leaf class, these functions will throw exceptions.
Leaf (Line, Rectangle, etc) : It represents a leaf objects in the composition. Leaf objects cannot have any children.
Composite (Picture): It stores child components.
Client: It manipulates the objects through the common interface exposed by the Component class.