SOLID: Single Responsibility Principle
One reason to change
"A class should have only one reason to change." - Robert Martin
Code always changes for a reason, and that reason to change always comes from someone in the business which wants something to work differently. eg dba wants to change a table, devs want to change a technology, UX wants to change the flow.
A class should only be able to be impacted by a single business role/person.
ie A class should never need to be changed due to requirements from two different people in the company. eg if the dba, ux designer, business analyst all want changes, those changes should affect different classes.
Single Responsibility Principle is NOT about ensuring that a change will impact only 1 class, because a change may impact many classes.
eg changing your database from sql to mongo, would mean you would have to make changes to multiple repo classes, as well as make changes to the class which injects the database connection.
The SRP is about ensuring that each class will respond to only one change.
ie if there were multiple changes required, a class should only be affected by one of those changes.
Example: Reasons to change
If we considered two required changes.
- A change in the data access provider (eg sql to mongo)
- Some Business logic change (eg give discounts to VIP users)
In a poorly designed system, a single class will have two reasons to change.
- The class will change because it does the business logic.
- The same class will also change because it contains data access logic.
In a SOLID system, there will be two classes, and each will have only one reason to change.
- one class, to do the business logic.
- and another class, to change data access.
Example: Good vs Bad modifications
Reason to change: Give VIP users a discount.
- Good change: Modifying the discount class.
- Bad change: Modifying the user class and discount class, but also all the classes which touch those two, like an extensions class, stored procedure, and login class.
Reason to change: Change SQL to MongoDb.
- Good change: Modifying Repos and connection.
- Bad change: Modifying all models and business logic classes because they contain SQL specific logic. (This could be hundreds of classes)
A "Reason to change" seems simple in these examples but it is not always so clear cut and is therefore a little flexible. Identifying a "Reason to change" is where the experience and "art" of software development comes in but there are some simple tips to consider. The following tips just give clues and are not definite violations.
- Describe your class in words, and if you have to use the word "and" in your description you should probably consider refactoring it. eg If you say "the class creates an invoice and prints it", your class is doing two things. Refactor it so that one class creates the invoice and another prints it.
- Think who in the business is most likely to ask for changes to a given class, if there is more than one role which will probably ask for changes then consider refactoring it. eg If a class has reasons to change coming from DBAs and UX and BI it is probably violating SRP
- Take note of who has modified the class in a given month and if multiple teams have had to work on the same class, it may be violating SRP so investigate further. ie if teams which work on very different functionality are modifying the same code, then it may be doing too much.
- Take note of the amount of classes injected into a given class if there are more than 3 classes, it may be violating SRP so investigate further. ie If many other classes are injected into a single class then it may be doing too much.
High cohesion and low coupling
Single Responsibility Principle is often described as a class should only do one thing. So developers go and put one method in each class because if there are two methods its doing two things? If you split your classes too much, you get a fragmented atomized system, which becomes more difficult to manage and understand. Encapsulation is destroyed, by the need to make everything public, in order for the class fragments to be able to communicate with one another. This is not ideal and does NOT improve readability and maintainability. Another quote from Robert Martin which is often forgotten but clears up this mess.
"Gather together the things that change for the same reasons. Separate those things that change for different reasons." - Robert Martin
The Single Responsibility Principle is actually proposing a balance between aggregation and division. Therefore you should achieve high cohesion and low coupling. Gather all the same responsibilities together, to achieve high cohesion. separate different responsibilities, to achieve low coupling.
The very simple reason for implementing SRP is that it improves readability and maintainability by creating high cohesion and low coupling.
When responsibilities become coupled it leads to fragile code and rigid code. Fragile code, is code that when a change is made to one part of the system it breaks other unrelated areas. Rigid code, is code which is difficult to change because a change requires changes in many other areas. Rigid and fragile code is difficult to refactor and leads to everyone distrusting the code base. By correctly using the Single Responsibility Principle you are able to restrict code modifications to one area of code per area of change. This means that the code can be safely refactored without breaking anything else.
NOTE: Implementing the SRP allows code reuse but make sure you understand "Dont Repeat Yourself" because you can cause fragility if done wrong. Basically if you are reusing code in modules which have different reasons to change you may cause fragility. Reason being, these unrelated modules are now coupled. So if one module needs a change to that shared code it will impact the other (possibly without you noticing at compile time).
Patterns which can help achieve SRP
- Facade pattern
- Decorator pattern
- Composite pattern