Skip to main content

SOLID: Single Responsibility Principle

About 5 years ago, I wrote a series of short articles on the SOLID principles, shared by email to my colleagues as a weekly, Monday morning read. They were then archived for reference to our internal knowledge base. That's getting a revamp and re-platforming now, and given their age, I'm not sure these pieces will be making the cut. Given it's fairly timeless stuff, seems a shame for them to disappear into the virtual ether, so I'll save them for posterity here.

The SOLID Principles

The SOLID principles are a well established set of tenets intended to guide software design toward a maintainable solution. They are widely applicable and operate at a high level, so are well worth considering for almost any type of application we are looking to build, using any framework or language.

For the full set of articles in the series, see:

S is for Single Responsibility Principle

The single responsibility principle (or SRP) simply states that a class should have one and only one reason to change, meaning that a class should have only one job. By following it, we likely end up with an application with a number of small, tightly focussed classes each responsible for one discrete function of the application.

We avoid the creation of classes with many methods and functions that deliver a wide range of functionality for the application.

By analogy, build a pair of scissors, a nail file and a corkscrew... rather than a swiss-army knife.

So that's fairly easy to state but whether or not a particular class adheres to it or not is to an extent a matter of interpretation, depending upon how widely you define its "responsibility". There are a number of ways to consider though whether a given class is too big or has too wide a responsibility.

One of these is with the concept of cohesiveness which can be examined by looking at the public and private methods and properties of the class. If these can be quite easily grouped - with one or more public methods and it's set of private helpers quite easily split from another set - that's probably a good candidate for splitting the class out into two.

Another related sign to watch out for is the creation of an iceberg class. This is one that from the outside looks to be fairly discrete with just one or a few, cohesive public methods. Under the covers though there's a lot more functionality going on, in the shape of many private helper methods. Again, there might be value in pulling out those private methods into one or more classes and passing them into the original class as dependencies.

A final one comes from when you consider unit testing the class - a good practice in and of itself of course, but it is often the case that designing your classes for testability also drives you to following good software design principles like the SRP.

It comes about when you find yourself considering that a particular private method of a class is a good candidate for a unit test - but you can't get access to create this test of course, because it's private. You could simply mark it as public or internal and be done with it but that's not a good idea - you don't want to be exposing private methods more widely just for the purpose of getting access to them for testing.

Rather it's likely a sign that the private method is encapsulating a responsibility that should be extracted out into it's own class - which then will be amenable to unit testing.

An example of this came from some work carried out for an education website that required in the accessing and processing of the school information held in an external system. Initially when this integration was developed, we had a class responsible both for retrieving the data from a web service and deserializing it into a strongly typed model objects.

A private method that dealt with parsing the response was a good candidate for unit testing - there was quite a lot of functionality going on in there and potential edge cases that would be valuable to have automated tests around. It's also quite straightforward to test in that it doesn't have any complex dependencies given that it just transforms a given JSON structure into an instance of a strongly typed class.

On analysis the class clearly had two responsibilities: firstly to manage the request to the web service and secondly to parse the response. Much better to break this up and have the parsing responsibilities extracted to a new class, that can be independently tested and passed in as a dependency to the service handler.