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.
/!\: This article has been originally published on my blog. If you are interested in receiving my latest articles, please sign up to my newsletter.
By the way, If you wants 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
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
class Journal {
string m_title;
vector<string> m_entries;
public:
explicit Journal(const string &title) : m_title{title} {}
void add_entries(const string &entry) {
static uint32_t count = 1;
m_entries.push_back(to_string(count++) + ": " + entry);
}
auto get_entries() const { return m_entries; }
void save(const string &filename) {
ofstream ofs(filename);
for (auto &s : m_entries) ofs << s << endl;
}
};
int main() {
Journal journal{"Dear XYZ"};
journal.add_entries("I ate a bug");
journal.add_entries("I cried today");
journal.save("diary.txt");
return EXIT_SUCCESS;
}
- 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.
class Journal {
string m_title;
vector<string> m_entries;
public:
explicit Journal(const string &title) : m_title{title} {}
void add_entries(const string &entry) {
static uint32_t count = 1;
m_entries.push_back(to_string(count++) + ": " + entry);
}
auto get_entries() const { return m_entries; }
//void save(const string &filename)
//{
// ofstream ofs(filename);
// for (auto &s : m_entries) ofs << s << endl;
//}
};
struct SavingManager {
static void save(const Journal &j, const string &filename) {
ofstream ofs(filename);
for (auto &s : j.get_entries())
ofs << s << endl;
}
};
SavingManager::save(journal, "diary.txt");
-
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.
Have Any Suggestions, Query or Wants to Say Hi
? Take the Pressure Off, You Are Just a Click Away.🖱️