Skip to content

Instantly share code, notes, and snippets.

@vishal-keshav
Last active January 26, 2019 14:49
Show Gist options
  • Save vishal-keshav/f754d5690ec924c044dfb95c49d79ecb to your computer and use it in GitHub Desktop.
Save vishal-keshav/f754d5690ec924c044dfb95c49d79ecb to your computer and use it in GitHub Desktop.
On Object oriented programming and design patterns in C++ part 9

Design Principles Part 9

Composition is a very important concept in software engineering. Small eleement creates larger and more complex elements. To understand this moe firmly, we can take an example of a case where we need to create GUI elements. Some GPU elements are standlone by itself, but some combines to constructs a bigger GPU elements.

Below code tries to do this.

Naive Implementation

#include "stdafx.h"
#include <iostream>
#include <vector>
#include <string>

using namespace std;

// Composite structure

class Element
{
protected:
	vector<Element*> elems;
	string elem_name;
public:
	/*Element(string name){
		elem_name = name;
	}*/
	virtual void show(){
		cout << elem_name << " show" << endl;
		for (Element* e : elems){
			e->show();
		}
	}
	virtual void hide(){
		cout << elem_name << " hide" << endl;
		for (Element* e : elems){
			e->hide();
		}
	}
	virtual void add(Element *elem){
		elems.push_back(elem);
	}
};

class Window : public Element
{
public:
	Window(string name){
		elem_name = name;
	}
};

class Button : public Element
{
public:
	Button(string name){
		elem_name = name;
	}

};

class Panel : public Element
{
public:
	Panel(string name){
		elem_name = name;
	}

};

int main()
{
	Window w1("w1");
	Button b1("b1");
	Button b2("b2");

	w1.add(&b1);
	w1.add(&b2);

	w1.show();

	w1.hide();

	Panel p1("p1");

	Button b3("b3");
	Button b4("b4");
	p1.add(&b3);
	p1.add(&b4);
	w1.add(&p1);

	w1.show();

	p1.show();
	p1.hide();

	int stop;
	cin >> stop;

	return 0;
}

Upon observing above code, it is clear that the concept of composition is not correctly implemented. Since, the element wise implementation of show and hide differs (both composite element and standlone element), Composite pattern is required to be implemented.

Composite Pattern

#include "stdafx.h"
#include <iostream>
#include <vector>
#include <string>

using namespace std;

// Composite structure

class UIElement
{
private:
	string name;
public:
	UIElement(string name) :name(name)
	{
	}
	const string& getName(/*const UIElement * const this*/) const // meaning of const in comment
	{
		return name; // Returns &name (since this is private memebr and cannot be destroyed)
	}
	virtual void show() = 0; // This method is abstract because this method needs to be 
	//implemented in different way by different implementation. Anotherthing is it has to be late bind call
	virtual void hide() = 0;
	/*virtual void add(Element *elem){
		elems.push_back(elem);
	}*/
};
// Composite is a UI element that holds UI Elelemnts
// Composte is an abstraction of Element
class Composite : public UIElement
{
protected:
	vector<UIElement*> elements;
protected:
	// Constructor is protected, so this UI Elelemt cannot be formed
	Composite(string name) :UIElement(name)
	{

	}
public:
	void add(/*Composite const this*/ UIElement * const element)
	{
		elements.push_back(element);
	}
	void remove(/*Composite const this*/ UIElement * const element)
	{
		// TODO: remove element from a vector
	}
};

// This is first implementation of composite.
// An implementation means, whose object is created to do specific work
// An abstraction means whose object is not created but whose pointers is use to do late-bind call (by holding the address of implementation)
class Window : public Composite
{

public:
	Window(string name) :Composite(name){
		
	}
	void show(/*Window* const this*/)
	{
		cout << getName() << " - show" << endl;
		for (UIElement *element : elements)
		{
			element->show();
		}
	}
	void hide(/*Window* const this*/)
	{
		cout << getName() << " - hide" << endl;
		for (UIElement *element : elements)
		{
			element->hide();
		}
	}
};
// Button is a implementation of component
class Button : public UIElement
{
public:
	Button(string name):UIElement(name)
	{
	}

	void show(/*Button* const this*/)
	{
		cout << getName() << " - show" << endl;
	}
	void hide(/*Button* const this*/)
	{
		cout << getName() << " - hide" << endl;
	}

};

class Panel : public Composite
{
public:
	Panel(string name) :Composite(name)
	{
	}
	void show(/*Panel* const this*/)
	{
		cout << getName() << " - show" << endl;
		for (UIElement *element : elements)
		{
			element->show();
		}
	}
	void hide(/*Panel* const this*/)
	{
		cout << getName() << " - hide" << endl;
		for (UIElement *element : elements)
		{
			element->hide();
		}
	}

};

int main()
{
	Window w1("w1");
	Button b1("b1");
	Button b2("b2");

	w1.add(&b1);
	w1.add(&b2);

	w1.show();

	w1.hide();

	Panel p1("p1");

	Button b3("b3");
	Button b4("b4");
	p1.add(&b3);
	p1.add(&b4);
	w1.add(&p1);

	w1.show();

	p1.show();
	p1.hide();

	int stop;
	cin >> stop;

	return 0;
}

Some important concept now:

  • An implementation means, whose object is created to do specific work
  • An abstraction means whose object is not created but whose pointers is use to do late-bind call (by holding the address of implementation)
  • Composite elements cannot be created, it can only be integrated with standlone elements. A composite is an element itself, but since the implementation of composite element varies, it should be an abstractions of an implementation.

Finally, the code below showcase how at runtime, we can identify the composites. Also, late bind call for deconstructor is discussed in the comments.

#include "stdafx.h"
#include <iostream>
#include <vector>
#include <string>

using namespace std;


// Composite structure

class UIElement
{
private:
	string name;
public:
	UIElement(string name) :name(name)
	{
	}
	const string& getName() const
	{
		return name; 
	}
	virtual void show() = 0;
	virtual void hide() = 0;
	virtual ~UIElement() {}; // This is for late bound call (important)
};
// Composite is a UI element that holds UI Elelemnts
// Composte is an abstraction of Element
class Composite : public UIElement
{
protected:
	vector<UIElement*> elements;
protected:
	Composite(string name) :UIElement(name)
	{

	}
public:
	void add( UIElement * const element)
	{
		elements.push_back(element);
	}
	void remove(UIElement * const element)
	{
		// TODO: remove element from a vector
	}
};

// This is first implementation of composite.
// An implementation means, whose object is created to do specific work
// An abstraction means whose object is not created but whose pointers is use to do late-bind call (by holding the address of implementation)
class Window : public Composite
{

public:
	Window(string name) :Composite(name){
		
	}
	void show(/*Window* const this*/)
	{
		cout << getName() << " - show" << endl;
		for (UIElement *element : elements)
		{
			element->show();
		}
	}
	void hide(/*Window* const this*/)
	{
		cout << getName() << " - hide" << endl;
		for (UIElement *element : elements)
		{
			element->hide();
		}
	}
};

class Button : public UIElement
{
public:
	Button(string name):UIElement(name)
	{
	}

	void show()
	{
		cout << getName() << " - show" << endl;
	}
	void hide()
	{
		cout << getName() << " - hide" << endl;
	}

};

class Panel : public Composite
{
public:
	Panel(string name) :Composite(name)
	{
	}
	void show()
	{
		cout << getName() << " - show" << endl;
		for (UIElement *element : elements)
		{
			element->show();
		}
	}
	void hide()
	{
		cout << getName() << " - hide" << endl;
		for (UIElement *element : elements)
		{
			element->hide();
		}
	}

};

int main()
{
	vector<UIElement*> elem = { new Window("w1"), new Button("b1"), new Panel("p1") };
	// At runtime, if we need to know which object is composite, we use dynamic_cast.
	for (UIElement* e : elem){
		Composite *composite = dynamic_cast<Composite*> (e);
		if (composite != nullptr){
			// It is a composite
			// If null pointer, then not Composite
		}
		// By this method, it can be know at the runtime that how many are composite and how many are component
	}
	// The below code is dangerous, it will deallocate the base class memory, but not the derived class memory.
	// To deallocate derived class memory also, we have made destructor of derived class virtual.
	for (UIElement* e : elem){
		delete e;
	}
	// At least there has to be one virtual function for VTABLE to be created.

	int stop;
	cin >> stop;

	return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment