Skip to content

Instantly share code, notes, and snippets.

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

Design Principles Part 5

We better revisit some of the concepts learned previously.

  1. Single responsibility principle: An object should perform only one work and not multiple unrelated work. basically, it should have one reason to change, not multiple reseason to change.

  2. Open close principal: Open for extension and closed for modification, when client wants to depend on varying implementation. This can be achieved with loose coupling.

Loose coupling can be achieved by creating abstraction for varying implementation. Implementation object should not be created by client. If client tries to create, they will need the class names, making the code tightly coupled with varying implementation.

There are 4 ways to implement factories:

  • Factory method: Create an interface (Abstraction) to create an object of implementation, but let the subclass (derived class) decide which class (implementation class) to instantiate.
  • Abstract Factory: Factory method + Create family of related object.
  • Builder: Factory method + Create objects in step by step fashion and return the complex object
  • Protoype: Create an object from an existing object's type
  1. Dependency inversion: Client do not use implementation name to create object, instead they receive the object that they need. In C++, this can be achived by creating the object globally and getting their address flow to the super class (base class) constructor. In super constructor call, store the address in a static vector.

Camera surveliance system

We can implement a camera surveliance system that implements all the concept learned previously. The system diagram can be shown in the comment section.

Basic Implementation

In this basic implementation, we implemented only camera driver system that can trigger the events.

#include <iostream>
#include <thread>
#include <vector>
#include <string>
#include <functional>
#include <algorithm>
#include <mutex>


//#include <Windows.h>

using namespace std;

// Multiple Object of this class can be created when subject wants to raise different event
// Device driver can raise different events at different point of time.
// When different object are created, it may need different signature(or data) to raise the event. This
// is why event class has to be templated.
// Event mechanism can be built using function<> object - There is a flexibility to create observer in 5 different ways:
// Global function, Static function, Instance method, Lambda, Functor.
// we are going with Instance method

// The template Tsignature can be anything (when we create a class)
// We are interpreting TSignature as any input type to the eventhandler functions.
template<class TSignature>
class Event
{
private:
	vector<function<TSignature>> eventHandlers; // Observers
	mutex sync;
public:
	// Registering Event Handlers
	void operator += (/*this*/ const function<TSignature>& eventHandler) {
		lock_guard<mutex> gaurd1(sync); // RAII pattern
		eventHandlers.push_back(eventHandler);
	}

	// Unregister event handler
	void operator -= (/*this*/ const function<TSignature>& eventHandler) {
		lock_guard<mutex> gaurd2(sync);
		compare_func = [&eventHandler](const function<TSignature>& eh) {
			return eh == eventHandler;
		}
		eventHandlers.erase(find_if(begin(eventHandlers), end(eventHandlers), compare_func), end(eventHandlers));
	}

	// Raise a notification
	// In future , event should be supplied with variable data
	// below is an example of variadic template
	// We can pass in variable number of arguments to raisnotification
	template<class... TArgs>
	void raiseNotification(/*this*/ TArgs args...)
	{
		lock_guard<mutex> gaurd3(sync);
		// range-for Iterator pattern
		for(const function<Tsignature>& eventHandler : eventHandlers)
		{
			// this is perfect forwarding, sends variable number of data
			eventHandler(forward<TArgs>(args)...); // function<> class is a functor; overloads () operator
		}
	}
};

// Assumed camera comes from a single company
class CameraDeviceDriver {
private:
	thread t;
public:
	// Creating different events
	// Observer pattern
	Event<void(CameraDeviceDriver*, int)> VideoFeed; // This is event which sents a int, address of sender must be sent
	Event<void(CameraDeviceDriver*)> Error;
	void Start(/*this*/)
	{
		thread t([this]() {
			int count = 1;
			while(true)
			{
				count++;
				this->VideoFeed.raiseNotification(count);
				Sleep(2000);
			} });
			this->t = move(t);
	}

};

int main()
{
	CameraDeviceDriver cdd1, cdd2;
	cdd1.Start();
	cdd2.Start();
	while(1){
		// System is always running
	}

	return 0;
}

In the above code we see following:

  1. Use of templated Event class. Even class gives a mechenism to add, remove and raise events as and when is occurs. Since, those events are nothing but a function call with any kind of signature, the template has to use TSignature.
  2. A notification can be raised with nay number of arguments. So raiseNotification has to accept variable number of inputs. That is where variadic template comes inot picture. Also, for perfect forwarding of those variable inputs, forward is being used.
  3. lock_guard guard has been used for RAII pattern.

Till now, the camera system was raising the notification with even mechanism, but there is no one to receive (or listens to). This is where the CameraReceiver comes into picture. There has to be only one object that is required to listens to all the camera ntifications. So this needs to implement a singleton pattern. Furthermore, the Camera Receiver observes the camera feed from camera device driver, hence it implements an observer pattern. It observes by registering some observer function to the notification channel.

Singleton Camera Receiver

#include "stdafx.h"
#include <iostream>
#include <thread>
#include <vector>
#include <string>
#include <functional>
#include <algorithm>
#include <mutex>


#include <Windows.h>

using namespace std;

// Multiple Object of this class can be created when subject wants to raise different event
// Device driver can raise different events at different point of time.
// When different object are created, it may need different signature(or data) to raise the event. This
// is why event class has to be templated.
// Event mechanism can be built using function<> object - This flexibility to create observer in 5 different ways:
// Global function, Static function, Instance method, Lambda, Functor
template<class TSignature>
class Event  
{
private:
	vector<function<TSignature>> eventHandlers; // Observers
	mutex sync;
public:
	// Registering Event Handlers
	void operator += (/*this*/ const function<TSignature>& eventHandler) {
		lock_guard<mutex> gaurd1(sync); // RAII pattern
		eventHandlers.push_back(eventHandler);
	}

	// Unregister event handler
	void operator -= (/*this*/ const function<TSignature>& eventHandler) {
		lock_guard<mutex> gaurd2(sync);
		compare_func = [&eventHandler](const function<TSignature>& eh) {
			return eh == eventHandler;
		}
		eventHandlers.erase(find_if(begin(eventHandlers), end(eventHandlers), compare_func), end(eventHandlers));
	}

	// Raise a notification
	// In future , event should be supplied with variable data
	template<class... TArgs> // variadic template (any number of any type)
	void raiseNotification(/*this*/ TArgs args...)  
	{
		lock_guard<mutex> gaurd3(sync);
		// range-for Iterator pattern
		for(const function<Tsignature>& eventHandler : eventHandlers)
		{
			//perfect forwarding, sends variable number of data
			eventHandler(forward<TArgs>(args)...); // function<> class is a functor; overloads () operator
		}
	}
};

// Assumed camera comes from a single company
class CameraDeviceDriver {
private:
	thread t;
public:
	// Creating different events
	// Observer pattern
	Event<void(CameraDeviceDriver*, int)> VideoFeed; // This is event which sents a int, address of sender must be sent
	Event<void(CameraDeviceDriver*)> Error;
	void Start(/*this*/)   
	{
		thread t([this]() { 
			int count = 1;
			while(true) 
			{ 
				count++;
				this->VideoFeed.raiseNotification(count);
				Sleep(2000);
			} });
			this->t = move(t);
	}

};

// This has to use observer pattern
class CameraFeedReceiver {
private:
	vector<CameraDeviceDriver> Cameras;
	CameraFeedReceiver()  
	{
	}
public:
	CameraFeedReceiver(const CameraFeedReceiver&) = delete;
	CameraFeedReceiver& operator=(const CameraFeedReceiver&) = delete;

	static CameraFeedReceiver cfr; 
	static CameraFeedReceiver& get()  {
		return cfr;
	}
	// will be called by all device drivers
	void feedReceiver(/*this = & of object*/CameraDeviceDriver* sender, int count)
	{
		//TODO: Check which device driver the object it is by comparing which sender it is
		// can be done using find_if()
	}

	void addCamera(CameraDeviceDriver& cdd)
	{
		cdd.VideoFeed += bind(&CameraFeedReceiver::feedReceiver, this, placeholders::_1, placeholders::_2);
		Cameras.push_back(cdd);
	}
	void removeCamera(CameraDeviceDriver& cdd) {
		cdd.VideoFeed -= bind(&CameraFeedReceiver::feedReceiver, this, placeholders::_1, placeholders::_2);
		//Cameras.push_back(cdd); //TODO: remove device driver object (we dont want to handle feed anymore becasue camera is removed)
	}
};


int main()
{
	CameraDeviceDriver cdd1, cdd2;
	cdd1.Start();
	cdd2.Start();

	CameraFeedReceiver &cfr1 = CameraFeedReceiver::get();
	cfr1.addCamera(cdd1);
	cfr1.addCamera(cdd2);
	while(1){
		// System is always running
	}

	return 0;
}

In the next part, we will implement the storage system with some more refactoring.

@vishal-keshav
Copy link
Author

image

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