Monday, September 07, 2009

SOLID part 1: Single Responsibility Principle

The Single Responsibility Principle (SRP) is the first of the five SOLID principles which governs the object-oriented design. They have been formulated by Robert Martin aka Uncle Bob and are universally recognized as good architecture.

This post is the first in a five-part series which will give an introduction to every principle. You may want to subscribe to Invisible to the eye feed if you want to stay tuned on new issues of this series.

There can be only one reason for a class to change.
This is the common formulation of SRP, the Divide et impera of software development. The meaning of this phrase is that when you're adding features to your application, two different, unrelated stories to implement should not affect the same class. What is implied by this principle is that every class you design should have only one responsibility, and often in software development change is the unit of measure: since your class Car have only one responsibility, only a change in the requirements of this responsibility should make you open the source file and modify the code of the class.

Here are some examples of classes which does not follow the SRP:
  • a Car which fills its fuel tank by itself
  • a CreditCard which knows how to charge itself (the classic Misko Hevery's example)
  • a CreditCardProcessor which knows how to do http requests
  • a Comment which sends mails
The scope of your application decides when responsibility should be splitted among more than one class: if braking and accelerating are complex processes, they should be factored out from the Car class, in two classes such as BrakeSystem and Engine respectively (or maybe Transmission, which will decouple Engine from the Car). Abstracting what is really complex is the point of this principle: if you need only to save the number of wheels in a Car you probably do not need to write a Wheel class whose instances will be placed in the Car one: it is a unuseful layer of indirection. If your wheels needed to know when they are totally consumed or are going to explode due to external solicitation, then they would become instanceof Wheels and would earn a class on their own: this is an example of refactoring. Until then, you are building a model of reality and you should not include a customer eye's color in their bank account information.

A rule of thumb to make this decision is to describe the behavior of a class with a single phrase. If you cannot formulate such a phrase or it contains the word and or other connectives, probably the behavior should be divided in more simple parts.
Particularly, entity classes (in respect to service ones) should have only the purpose to maintain state along with their business, inherent behavior. That's why a Comment instance contains name, text and mail fields but not a method warnAuthor(), which sends mails, to call on subsequent comments insertion.
The advantages of religiously following SRP are:
  • classes are more reusable, since you can pick only the one which implements a specific behavior without reusing a God class which can do everything and depends on everything.
  • classes are shorter and simpler to maintain.
  • design is more fine-grained, because every bit of behavior has its place in a small class. Knowing what you are going to modify it's half of the work in object-oriented development: with smaller classes, it will be obvious where a change belongs.
  • writing small and cohesive classes leads to testable code, while writing God classes leads to a non-testable ball of mud. A maintainable system is composed by a graph of object whose classes depends on each other for collaboration: this picture is obtained with dependency injection techniques.
Think of a complex object, like your computer: it composed by a moltitude of objects, but it respects the SRP; every component has its responsibility, from the resistor to the monitor, and complex components are built by wiring together smaller and cohesive ones.
Now that we are inspired by the principle, let's factor out some responsibilities from our example classes:
  • a GasStation or a GasPump will fuel a Car's tank
  • a CreditCardProcessor should take care of charging a CreditCard
  • a CreditService will insulate CreditCardProcessor from the Internet
  • a CommentRepository will call a Mailer or a CommentSubscriber whenever a new Comment instance is add to it
I hope you start to consider Single Responsibility Principle whenever you are designing a component of your application. It's a bit difficult to grasp at the exact level of detail, but if you find the right level of abstraction that is needed, the benefit will be beautiful and simple code; which, for a complex application, is a great result.

* The object in the image at the top of the post is Bicycle Wheel from Marcel Duchamp, a surrealist artist. It is an artwork, but to me is a perfect symbol of an object that does too much.

5 comments:

Andrea Cerisara said...

You're right saying that SRP leads to a testable code, and I think it's easier to apply SOLID principles using a Test-Driven development rather than a classic one (you've to think about them, while with TDD they're automatically applied).

Giorgio said...

I will treat extensively the testing point of view in the remaining parts, particularly in Dependency Inversion principle.

Rogerio Liesenfeld said...

Striving for high cohesion is important, of course, and I understand the point of the principle.
But I find the formulation of SRP too vague and confusing.
What exactly is a "responsibility"? What if I aggregate multiple low-level responsibilities in a single high-level one?
The rule of thumb of formulating a phrase won't always work, I suspect.
Even your example that a "Comment instance contains name, text and mail" can be said to violate SRP, because in practice the Comment entity should only contain user id and text, with user name and e-mail in a separate UserProfile entity.
See what I am trying to say? SRP does not provide clear guidance.

Giorgio said...

That's why programming is hard. SRP is a principle, not an extensive guide or checklist: in my opinione there is no unique answer to the question "Does a comment with author and text violate SRP?"; it depends on the particular model you are building in an application. If emphasis is given on particular concepts like the author identity, indeed a User/Author class would benefit the design; if the identity is not needed or maintained it is overengineering.
TDD helped me a lot since I found myself adding only the needed classes and doing a bit of design at the time.

coding craft said...

Great post giorgio. SRP can lead to a very clean and maintainable code. A very good indicator of the degree to which a code follows SRP is the kind of name that classes have. I have shared an interesting thought on SRP here. Cheers.

ShareThis