Single Responsibility Principle
Today, we’ll dive into the S of the Solid principles: The Single Responsibility Principle. But before we start, just a quick refresher on Solid.
SOLID is an acronym for a set of five software development principles, which if followed, are intended to help developers create flexible and clean code. The five principles are:
The Single Responsibility Principle — Classes should have a single responsibility and thus only a single reason to change.
The Open/Closed Principle — Classes and other entities should be open for extension but closed for modification.
The Liskov Substitution Principle — Objects should be replaceable by their subtypes.
The Interface Segregation Principle — Clients should not be forced to depend upon interfaces that they do not use.
The Dependency Inversion Principle — Depend on abstractions rather than concretions.
So, the single responsibility principle. As the name suggests, each class in a program must have a single responsibility for only one part of the program’s functionality. But that seems easier than it is. What exactly is a one part of a program, and how to know when to separate functionality? It’s too simple to say that a class should only do one thing.
Robert C. Martin expresses the principle as ‘Gather together the things that change for the same reasons. Separate those things that change for different reasons’, and more recently ‘This principle is about people’. That should point us into the right direction.
When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or a single tightly coupled group of people representing a single narrowly defined business function. This means that a software module or class should have one responsibility for that particular group of people.
It is easier to explain this by means of an example. Let’s take a look at this following class:
type TShip = class private FPosition: TPoint; FHeading: Integer; FSpeed: Integer; FCargoLoad: string; public procedure SetHeading(NewHeading: Integer); procedure SetSpeed(NewSpeed: Integer); function GetCoordinate: TPoint; procedure PlotCourse; procedure LoadCargo(NewCargo: string); procedure PrintCargo; procedure ReportPosition; procedure CalculateProfit; end;
This is a class that is doing a couple of things, all about managing a ship’s position and course, its load and some reporting things. As this is a brief example to show how to think about the Single Responsibility Principle, don’t pay too much attention to the details of the code itself; it is all about the large overview of the structure of this particular class.
I think we all know these kind of ‘God’ classes. Usually packed with lots of functionality and code, and managing one particular part or module of your program. The question is, if we need to make some changes to this class, how can we refactor this class to make sure we gather together the things that change for the same reason, and separate those things that change for different reasons.
Let’s stop for a moment and think about responsibilities of this code regarding to the (group of) people. We can define a couple of specific people that will have some responsibility regarding the ship’s management, direction and heading, and reporting tools. So let’s say in this case we define a skipper, a navigator, a cargo load master and a financial manager. If you think of these different roles, it’s suddenly very easy to separate this class into different modules, with just one responsibility to that particular role. We should have a class for setting the heading and power (skipper), one for managing position and plotting the course of the ship (navigator), one for managing the load of this ship (cargo load master), and one for all our (financial) reporting (financial manager).
Our new classes might now look like this:
type TShipLocation = class private FPosition: TPoint; public function GetCoordinate: TPoint; procedure PlotCourse; procedure ReportPosition; end; TShipMovement = class private FHeading: Integer; FSpeed: Integer; public procedure SetHeading(NewHeading: Integer); procedure SetSpeed(NewSpeed: Integer); end; TCargo = class private FCargoLoad: string; public procedure LoadCargo(NewCargo: string); procedure PrintCargo; end; TShipReport = class public procedure CalculateProfit; end; TShip = class private public // reference to subclasses end;
Would you have done the same if you didn’t think of the people behind the class’s responsibilities? Maybe, but I can imagine the ShipLocation and ShipMovement class could have ended up in the same class.
So, what happens now if we got a feature request from the skipper to add a bow thruster to make it easier to steer the ship in small canals? Just make this change in the ShipMovement class, without affecting any of the other classes. And if we want to implement a new cargo load system? Just alter the Cargo class, again without touching any of the other classes.
I hope by now you see why the single responsibility principle is really about people, or actors, and the responsibility of the functionality of modules or classes of your program in relation to these people. And of course, you can apply this on different levels of your program, from modules to classes to specific functions.
If you always have this principle in mind when refactoring or designing a module or class, I’m sure your code will be better maintainable and is easy to change.
Become a GDK Delphi hero! Subcribe here to our Delphi mailing list!