Segregation of responsibilities with the Single Responsibility Principle
This article, the first of the 5-part article on S.O.L.I.D design principles, is about the “S,” Single responsibility principle made famous by Robert C. Martin (Uncle Bob)in his paper, https://web.archive.org/web/20150924054349/http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf.
S — Single-Responsibility Principle
4 C’s — Concern, Coupling, Cohesion & Change
A concern is defined as a set of information that has the potential to affect, alter, change the state of any entity. In computer science, concerns are everywhere. Logical grouping of operations and data is another definition of concern. Examples include a series of actions to fetch data from a server. A read-write database operation etc.
A program has many such concerns dealing with a different use case, different business logic & states. Parts eventually have to interact with each other to create a whole, and concerns are no different. This relation is known as coupling. Passing data between concerns, referencing a type within itself, etc
Cohesion defines how efficient a module is as a whole viz a viz coupling. Lower the coupling, the higher the cohesion. In simple terms, it’s the strength of the module within itself. It’s desirable to have a higher level of cohesion for reduced dependencies.
Definition & References
In the original paper by Uncle Bob (linked above), SRP is described under the section
Principles of Package Architecture or more specifically The Common Closure Principle & The Common Reuse Principle
SRP deals with grouping of similar concerns and changes. Robert Martin defines it as
Gather together the things that change for the same reasons. Separate those things that change for different reasons.
The most widely used and popular definition of SRP is
A class or module should have one, and only one reason to be changed.
SRP with the 4 C’s
With the definition of SRP and the 4 C’s clear let’s see SRP in action with some code.
SRP states that class or structure or package or module or any ADT for that matter should represent only one concern. Moreover, the reason to change that should be from the same source either the same person or a tight group of entities with the same agenda.
For e.g. the same business unit or the same functional source.
If we were to create an instance of an Ironman suit for Tony Stark to suit up and save the universe, it would closely resemble the above. We won’t settle for any older model. We only use the best so the latest and greatest model, “Mark 85”. The suit obviously has the ability to fly() and fight() based on Tony Stark’s commands. What are the “concerns” here? The ability to fly() and the ability to fight(). How does the suit lift off? How does it attack? What weapons are deployed? How is the suit powered? The logic, data, and process for the suit to fly at a high altitude, use limited weaponry to restrict damage, alternative power sources are safely enclosed within the Ironman class. This class is only concerned with operations and data associated with the suit. Any change to the mode of flying or a new weapon added will affect only this class. Superb cohesion. After all, Ironman is the best!
So the IronMan in isolation continues to beat up the bad guys and save the world. However, now he is part of the Avengers. So Nick Fury wants Tony to report to him, and hence a new action is expected of the IronMan. Report(). But where should he report to?
The Stark Tower?
Maybe the Hellicarrier? Or
perhaps the Shield headquarters?
The logic for reporting is now introduced to the Ironman class
So the inevitable change has happened in the Ironman class.
What is that change? A new “concern,” an expectation i.e., reporting().
What’s the source of this concern? This new requirement?
Maybe Nick Fury, Steven Rogers or even Pepper Potts. They all want Tony to report to them at some stage.
Bottom line, somebody other than Tony Stark himself. Somebody other than the Ironman. Somebody or something from the outside
In simple terms, An external entity has forced Ironman to change.
A new coupling has taken birth.
Price of change
Any change to the other end of the coupling i.e., reporting destination will result in a change in the IronMan class.
E.g., The Mad Titan Thanos destroyed the StarkTowers, and the Ironman is on the way reporting to the StarkTowers. BOOM! He crash-lands as StarkTowers is no longer there, so now we have to rework on the Ironman to remove stark towers from the reporting() method. Some more changes! This additional coupling with the reporting() has resulted in a change to the Ironman class.
Single source for change
Whenever a class has to change for a reason other than itself, it violates the SRP. A class should not change for a purpose it does not primarily serve, or for a service, it does not provide. Any class should only be changed from one source. In other words, a class should be modified only for the sake of one concern. The one it serves. The changes can be plenty, mind you, but the request for change should come only from one source. In simple terms, A Login module should not change because the Checkout module wants some changes or vice versa. It should only change if there is some change in the Login requirement
For e.g, we can perform complex operations like remodel(), warpSpeed(), advanceMachinery() etc on the Ironman. Because the Ironman class serves the purpose of building the best suit. So it is perfectly fine to work on bettering the Ironman constantly. However, if Ironman keeps getting changed because the Avengers have split, Pepper Pots does not approve, or Captain America goes rogue, well that’s when the Ironman class is handling more than one thing. Ironman's concerns are getting modified from outside. Its couplings are binding. And Tony has a giant ego as we know!
Ironmans sole responsibility should be to build the best suit, which can destroy any enemy. It should function well in space, underwater or alien territory. However, it has no business being affected by other concerns.
SRP is all about limiting the impact of changes as opposed to the number of changes. By making a class or an ADT perform a single group of functions, the impact of change is minimized. As Uncle Bob said, Classes that change together belong together. Classes that are not reused together should not be grouped.