There is much said about the SOLID principles. If you’re not familiar, the SOLID principles of Object Oriented development are:
- Single Responsibility Principle
- Open/Closed Principle
- Liskov Substition Principle
- Interface Segregation Principle
- Dependency Inversion Principle
Each of these are important, but the one that probably gets the most coverage is the Single Responsibility Principle. Simply stated, each class should only do one thing.
Of course this sounds good in principle but when dealing with actual code, this becomes far more nuanced and complex. Where should the responsibilities of a class end? Depending on how you define the class’s responsibilities, you can struggle with and justify a lot of bloat in your code. In a cart class, should it handle generating the subtotal or just hold the items and let someone else do the math? Is that one of its responsibilities? What about if we add a “save for later” feature? Is that the cart’s responsibility or someone else’s?
As you struggle with these questions, you can spend a lot of time debating the right way to craft your code.
When you’re having trouble deciding what functionality should or should not be the responsibility of a given class, there’s an alternative interpretation of the Single Responsibility Principle that can often lead to good results. I don’t recommend utilizing this method exclusively, but instead include it in your analysis when asking yourself “should this class/component include this functionality?”
That is the “Reason for Change”. Ask yourself: what reasons would cause the class to change, and if it changes, what functionality would be affected by that change? Then work towards the ideal that a class would have only one reason to change.
Let’s say that you’re working on a system that needs some real-time communication with a custom websocket. We not only need to connect and disconnect, but we also need to validate the identity of the client. So we come up with the following public methods for a “SocketConnection” class:
Seems reasonable doesn’t it? The class is responsible for the communication with the server, and all things related to that are in the class.
But think about this: connection & disconnection are likely to change together. If the way that we connect changes, then it’s reasonable that the disconnection may change as well. Those two piece of functionality belong together and are likely to reuse a lot of functionality between them.
On the other hand the authentication piece is possibly very different from the other two. If we change how we authenticate after we connect, we very likely wouldn’t change how we connect or disconnect.
So the reasons for change are different. This implies that the responsibilities should be separated out into two different classes. That leaves the class that simply connects and disconnects with only one reason to change: The method of connection has changed. And the other class has a single reason to change as well.
Now don’t think that what we should end up with is a million different classes each with one method. But instead look at the class not as a collection of “related” functionality, but instead as a collection of “interdependent” functionality. Then separate out stuff that isn’t interdependent into collaborators and dependencies. A great way to identify this is to ask if the class has a single reason for change.
In the end you’ll have to make your own judgements, but just getting in the habit of asking yourself the above questions will help you design better, more robust, more well-factored classes.
Subscribe to the Thinkster.io Newsletter for more content like this.