Single Responsibility Principle in C++: SOLID as a Rock

Written by IndianWestCoast | Published 2020/05/05
Tech Story Tags: c++ | cpp | programming | design-patterns | solid-principles | single-responsibility-principl | coding | solid

TLDR 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. The Principle states that classes should be cohesive to the point that it has a single responsibility, where responsibility defines as "a reason for the change"via the TL;DR App

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.
/!\: Originally published @ www.vishalchovatiya.com.
By the way, If you haven't gone through my previous articles on design principles, then below is the quick links:

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. i. Things related to Journal, ii. Saving the Journal
  • 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.

Written by IndianWestCoast | Software Developer⌨, Fitness Freak🏋, Geek🤓, Hipster🕴, Blogger👨‍💻, Productivity Hacker⌚
Published by HackerNoon on 2020/05/05