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
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.
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
* 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:
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).
I will treat extensively the testing point of view in the remaining parts, particularly in Dependency Inversion principle.
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.
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.
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.
Post a Comment