Card image cap

Understanding SOLID principles - Single Responsibility

SOLID Principles  • Posted 8 months ago

Flexibility, Maintainability and Extensibility are some of the buzzwords we often hear when dealing with developing application softwares. While these are the desired characteristics of an ideal application component developed, we seldom forget to keep them in mind while designing our strategies owing to tighter deadlines and various other factors which often go hand in hand with development. And there are simple principles which help us design and develop some really good components which also comply with the desired characteristics of an ideal software, and we call them the SOLID principles.

SOLID is an acronym coined for five guiding principles which describe the things we need to keep in mind while designing a component or developing a system. In this article we shall discuss the first of the five principles - the Single Responsibility Principle (S).

Single Responsibility - The first step towards flexibility

Single Responsibility Principle or shortly called SRP is the first letter of SOLID (S) which talks about a component's flexibility and maintainability. The rule states that:

"A component (a class, method or a function) must contain only one function to perform. It must have only one reason to change."

We generally tend to stuff up a single component (say a class or a method) with all the logic we need it to implement without keeping in mind of its future. What would happen if some of the dependencies of it need to change? Or how would that component embrace any extension for a part of its functionality. We call such components as God Objects.

A God object - a satanic effect:

A God object can be defined as an object which knows more than what it needs to know, or does more than what it is expected to do. All the components we develop can become god objects if we don't have a vision on what we expect that particular component to perform or what that component needs to actually contain. For example let's assume we have an API for Readers and Users which caters a variety of operations on the entities. Let's say we have a class LogicController which is the only single endpoint for all our operations on Users and Readers. Now if we take all the CRUD operations (Create, Retrieve, Update and Delete) on both Reader and User entities into account, then we have a fat controller class with eight endpoints. This makes the controller not only hard to understand for a newbie but also difficult to maintain, talking about extensibility is a waste already. Such a class is called as a God object and Single Responsibility principle talks about avoiding such heavy lifting God objects by assigning a single responsibility for a single class to stick to: the LogicController must hold only either User actions or Reader actions but not both. This helps in the overall maintainability of the classes at lower level and enhance readability at the higher level.


    public class LogicController : ControllerBase
    {
        public LogicController()
        {
        }

        [HttpGet]
        [Route("allreaders")]
        public List<Reader> GetReaders()
        {
	    var store = new ReaderStore();
            var readers = store.Readers.ToList();
            return readers;
        }

        [HttpGet]
        [Route("allusers")]
        public List<User> GetUsers()
        {
	    var store = new ReaderStore();
            var readers = store.Users.ToList();
            return readers;
        }
    }

The driving factors of Flexibility - Cohesion vs Coupling:

To understand the second statement of the principle, "only one reason to change", we have two indicators which can explain how good is the component to be flexible for change - Cohesion and Coupling. In any given component, the Cohesion describes how closely the class elements are related to each other. The Coupling on the other hand explains closely two classes or components are related to each other. Let's take the example of the LogicController class we discussed previously. We have all the functionalities and respective dependencies related to Readers and Users together in a single encapsulation, and we can understand clearly that all the things related to Users entity are unnecessary for Readers entity and vice versa. Hence in this case there is a low cohesion among the members of the component LogicController. On the other hand we have an instance of ReaderStore class being created inside each method of LogicController which creates a sort of link between the components LogicController and ReaderStore, making things tougher to maintain and to extend. Such instantiations and dependencies indicate a higher degree of Coupling across the components.

"An Ideal component should have a higher degree of Cohesion and a lower degree of Coupling"

Poor Responsibility - Difficulty in Testing:

Another factor that is impacted by a poor sense of Responsibility is the testability of the component. A component which handles multiple responsibilities and has high degree degree of coupling is less easily testable, since we would end up writing more logic for test cases and their execution. Such a component which is hard to test is prone to more and more issues when moved to real-world request loads.

And hence a component when assigned to only a single responsibility for itself can have all the dependencies and functionality related to only that responsibility and this results in a higher degree of cohesion. And since we utilize only those dependencies which the component requires, we end up in components which are lighter in weight and are of lower degree of coupling. This also results in effective test implementations which can result in fewer issues. As a whole, having a Single Responsibility for components can impact a lot on the quality of the components developed. And by applying the SRP on the LogicController, we can break the class into two different classes each for Reader and User; say ReaderController and UserController. And we design the dependencies such that the components are loosely coupled with each other and the result can be as below:


    [Route("api/[controller]")]
    [ApiController]
    public class UserController : ControllerBase
    {
        ReaderStore store;

        public UserController()
        {
	    this.store = new ReaderStore();
        }

        [HttpGet]
        [Route("allreaders")]
        public List<User> GetReaders()
        {
            return store.GetUsers();
        }
    }

Same can be applied to ReaderController which holds a single reason for existence: the Reader entity.

In this way, we can follow the above steps and have our components comply to SRP.

We use cookies to provide you with a great user experience, analyze traffic and serve targeted promotions.   Learn More   Accept