This article is the first part of a five-part series about SOLID as Rock design principle series. The SOLID design principles focus on developing software that is easy to maintainable, reusable & extendable. In this article, we will see an example of the Single Responsibility Principle in C++ along with its benefits & generic guideline.
By the way, If you want to directly jumps to other design principles, then below is the quick links:
- SRP – Single Responsibility Principle
- OCP – Open/Closed Principle
- LSP – Liskov Substitution Principle
- ISP – Interface Segregation Principle
- DIP – Dependency Inversion Principle
The code snippets you see throughout this series of articles are simplified not sophisticated. So you often see me not using keywords like override
, final
, public
(while inheritance) just to make code compact & consumable(most of the time) in single standard screen size. I also prefer struct
instead of class
just to save line by not writing “public:
” sometimes and also miss virtual destructor, constructor, copy constructor, prefix std::
, deleting dynamic memory, intentionally. I also consider myself a pragmatic person who wants to convey an idea in the simplest way possible rather than the standard way or using Jargons.
Note:
- If you stumbled here directly, then I would suggest you go through What is design pattern? first, even if it is trivial. I believe it will encourage you to explore more on this topic.
- All of this code you encounter in this series of articles are compiled using C++20(though I have used Modern C++ features up to C++17 in most cases). So if you don’t have access to the latest compiler you can use https://wandbox.org/ which has preinstalled boost library as well.
Intent
A class should have only one reason to change
In other words, SRP states that classes should be cohesive to the point that it has a single responsibility, where responsibility defines as “a reason for the change.”
Motivation: Violating the Single Responsibility Principle
|
|
- Above C++ example seems fine as long as you have a single domain object i.e.
Journal
. but this is not usually the case in a real-world application. - As we start adding domain objects like
Book
,File
, etc. you have to implement save method for everyone separately which is not the actual problem. - The real problem arises when you have to change or maintain
save
functionality. For instance, some other day you will no longer save data on files & adopted database. In this case, you have to go through every domain object implementation & need to change code all over which is not good. - Here, we have violated the Single Responsibility Principle by providing
Journal
class two reason to change i.e.- Things related to
Journal
- Saving the
Journal
- Things related to
- Moreover, code will also become repetitive, bloated & hard to maintain.
Solution: Single Responsibility Principle Example in C++
- As a solution what we do is a separation of concerns.
|
|
Journal
should only take care of entries & things related to the journal.- And there should be one separate central location or entity which does the work of saving. In our case, its
SavingManager
. - As your
SavingManager
grows, you have all the saving related code will be at one place. You can also templatize it to accept more domain objects.
Benefits of Single Responsibility Principle
=> Expressiveness
- When the class only does one thing, its interface usually has a small number of methods which is more expressive. Hence, It also has a small number of data members.
- This improves your development speed & makes your life as a software developer a lot easier.
=> Maintainability
- We all know that requirements change over time & so does the design/architecture. The more responsibilities your class has, the more often you need to change it. If your class implements multiple responsibilities, they are no longer independent of each other.
- Isolated changes reduce the breaking of other unrelated areas of the software.
- As programming errors are inversely proportional to complexity, being easier to understand makes the code less prone to bugs & easier to maintain.
=> Reusability
- If a class has multiple responsibilities and only one of those needs in another area of the software, then the other unnecessary responsibilities hinder reusability.
- Having a single responsibility means the class should be reusable without or less modification.
Yardstick to Craft SRP Friendly Software in C++
- SRP is a double-edged sword. Be too specific & you will end up having hundreds of ridiculously interconnected classes, that could easily be one.
- You should not use SOLID principles when you feel you are over-engineering. If you boil down the Single Responsibility Principle, the generic idea would be like this:
The SRP is about limiting the impact of change. So, gather together the things that change for the same reasons. Separate those things that change for different reasons.
- Adding more to this, If your class constructor has more than 5-6 parameters then it means either you are not followed SRP or you are not aware of builder design pattern.
Conclusion
The SRP is a widely quoted justification for refactoring. This is often done without a full understanding of the point of the SRP and its context, leading to fragmentation of codebases with a range of negative consequences. Instead of being a one-way street to minimally sized classes, the SRP is actually proposing a balance point between aggregation and division.