Being Singly Responsible

The theme of our latest kata was the Single Responsibility Principle [PDF]. The Single Responsibility Principle says that every class should have just one responsibility, or alternatively just one reason to change.

For an easy example, consider a Screen class that represents a computer monitor. You might give it members like driver, connectionType (VGA or DVI), windowsDisplayed, and draw(Rectangle r). It’s pretty easy to see that these fall into two categories – some are attributes of a Device that the operating system talks to, and others are attributes of a Display that programs want to draw on. A remove() method might be in either category – it might remove the device, or remove the objects on the display! We can find plenty of examples of this kind of confusion in some of our larger and less tame domain classes.

Like the Screen example, physical objects, on which we often model our software objects, have multiple responsibilites. In fact a Screen may play more roles – accountants see it as a depreciable capital item and the IT department see it as a trackable asset, for example. Where those responsibilities appear closely linked, it’s very tempting to combine them in a single object, and hard then to see that they should be separated. This makes the principle easy to state, but difficult to apply.

For example, consider an Employee. Two things we may want to do are to evaluate the employee and to pay the employee a salary. These are closely linked in our minds – we expect a positive result in one to cause a positive change in the other! But there is no reason to link the code, though it seems natural; we will have much more flexibility if changing the evaluation mechanism doesn’t mean having to change the payroll system also. This promotes safety, too – the less we can touch a sensitive system like payroll, the better.

requesting files from the ProductStore where our application artifacts live,

releasing those files, and

reporting on status.

That’s a lot for one Groovy class to handle! After applying the SRP, we have an IdService that assigns IDs (which incidentally makes it easy to verify that the assignment is threadsafe), a ProductStoreService that handles interaction with the ProductStore, and other services that copy and release .war files. The ReleaseService is responsible for orchestrating these actions and reporting on their status, which is much simpler and easier to test.