Single Responsibility Principle: SOLID design Principles

Share the Article

What is Single Responsibility Principle

Single Responsibility principle is one of the basic principle for object oriented class designing. It is also contributing first “S” in SOLID acronym where S stands for Single Responsibility Principle. This principle tries to give future modifiability and maintainability to an application.

As it clear from the name, ” Single Responsibility” insists us to design a class to handle only single task. In other words, we should not create classes, such that they are doing multiple tasks or jobs. If our classes are handling multiple tasks then in future maintenance of such application will be very difficult. This is because updates done in one place of class will impact several places and leads to code pollution.

Basically, we should have only one reason or need for updating our class not multiple.

Why

Lets us try to understand the need of Single Responsiblity Principle by taking an example of an application performing below jobs:

  1. Selecting Computer components
  2. Assembly of components manually
  3. Dispatching assembled computer through Trains

Example not using Single Responsibility Principle

Lets us try to design above application without “Single Responsibility Principle” first.

Design a class Computer performing all 3 jobs:

class Computer { string m_cpu; string m_screen; string m_keyborad; public: void selectComponent(string cpu, string screen, string keyboard) { } void assembleComputer() { cout<< " Assemble component manually"<<endl; } void dispatchComputer() { cout<<"Computer Dispatch by Train"<<endl; } };

As seen above, we have designed a class Computer which is handling multiple tasks, such as, selecting components, assembling them and finally dispatching computer.

Below is the full application:

#include<iostream> //main header for io using namespace std; //for namespace class Computer { string m_cpu; string m_screen; string m_keyboard; public: void selectComponent(string cpu, string screen, string keyboard) { } void assembleComputer() { cout<< " Assemble component manually"<<endl; } void dispatchComputer() { cout<< " Computer Dispatch by Train"<<endl; } }; int main() //Client-code { Computer c1; c1.selectComponent("single core","Normal Screen","Keyboard"); c1.assembleComputer(); c1.dispatchComputer(); return 0; }

Suppose, now we have future requirement to update above application with below needs:

  • Add more components such as Speaker, Touch screen, Bluetooth headset
  • Assembly should happen in electrical mode also (not only manual)
  • Dispatch should support – Truck , Aeroplan, also

As seen above, only one class Computer is doing all jobs. Therefore, we need to update Computer class for all above cases. Whoever, maintaining Computer class needs to keep modifying it. Due to this our application is not easily adapting for future needs.

In order to write application which we can very easily adapt for future needs, the “Single Responsibility Principle” is the best suit.

How

Lets us try to design same application using “Single Responsibility Principle”.

Thumb rule – Give only one responsibility to one class and should have only one reason for getting updated.

Lets us try to update above application with above rule. Since above application is doing 3 jobs, such as, selecting components, assembling components and dispatching computer. Therefore, now we shall break down this and create one class for each responsibility as below:

First Job – Selecting Components

Firstly, design class Computer which handles only one job, i.e., selecting components:

class Computer { string m_cpu; string m_screen; string m_keyboard; public: void getComponent( ) { } };

Second Job – Assemble Computer

Secondly, design class AssemblyComputer which is also handling single job of assembly of components.

class AssemblyComputer { public: void assemblyComponent() { cout<< " Assemble component manually"<<endl; } };

Third Job – Dispatch computer

Finally, design class DispatchComputer to handle job of dispatching of computer.

class DispatchComputer { public: void dispatch() { cout<< " Computer Dispatch by Train"<<endl; }; };

Complete Example using Single Responsibility Principle

#include<iostream> //main header for io using namespace std;//for namespace class Computer { string m_cpu; string m_screen; string m_keyboard; public: Computer(string cpu,string screen,string keyboard){}; void getComponent() { } }; class AssemblyComputer { Computer *m_computer; public: AssemblyComputer(Computer *c1) { m_computer=c1; } void assemblyComponent( ) { m_computer->getComponent(); cout<< " Assemble component manually"<<endl; } }; class DispatchComputer { AssemblyComputer *m_assembledComputer; public: DispatchComputer(AssemblyComputer *a1) { m_assembledComputer = a1; } void dispatch() { m_assembledComputer->assemblyComponent(); cout<< " Computer Dispatch by Train"<<endl; } }; int main() { Computer c1("Single Core","Screen", "Keyboard" ); AssemblyComputer a1(&c1); DispatchComputer d1(&a1); d1.dispatch(); return 0; }

As seen above, now in our application each class is doing only single job. Therefore, future extensions are very easy to design. For instance, If there is an update in assembly part, then, other parts, like, dispatchComputer shall not get impacted.

Adding new requirements in same example

Lets us try to update above application by supporting different dispatch mode, i.e., through truck and aero plane (in addition to train)

#include<iostream> //main header for io using namespace std; //for namespace class Computer { string m_cpu; string m_screen; string m_keyboard; public: Computer(string cpu, string screen, string keyboard) { }; void getComponent() { } }; class AssemblyComputer { Computer *m_computer; public: AssemblyComputer(Computer *c1) { m_computer=c1; } void assemblyComponent( ) { m_computer->getComponent(); cout<< " Assemble component manually"<<endl; } }; class DispatchComputer { AssembleyComputer *m_assembledComputer; public: DispatchComputer(AssembleyComputer *a1) { m_assembledComputer =a1; } void dispatch(string dispatchmode) { m_assembledComputer->assembleyComponent(); cout<< " Computer Dispatch by "<<dispatchmode<<endl; } }; int main() //Client-Code { Computer c1("Single Core","Screen", "Keyboard" ); AssembleyComputer a1(&c1); DispatchComputer d1(&a1); d1.dispatch("Aeroplane"); return 0; }

As seen above, now we have updated dispatchComputer class. Here, the function dispatch(string dispatchmode) shall accept a string argument. Hence, with this minimal change in one class, new requirement is easily supported in above application.

Pros & Cons of Single Responsibility Principle

Pros

  • Firstly, it introduces loose coupling between classes, therefore, future adaptation is very easy.
  • Secondly, the code becomes very easy to understand and maintain. Clean code.
  • Finally, it becomes very easy to write unit test cases.

Cons

  • This needs very careful design approach, otherwise it may lead to too many classes. In worst case, every function may become one class.

Main Funda: Single Responsibility Principle promotes modularization of design.

Advanced C++ Topics

Singleton Design Pattern
Factory Method Design Pattern
Builder Design Pattern
How std::forward( ) works?
How std::move() function works?
What is reference collapsing?


Share the Article

Leave a Reply

Your email address will not be published. Required fields are marked *