44

The design pattern the first letter in this acronym stands for is the Single Responsibility Principle. Here is a quote:

the single responsibility principle states that every object should have a single responsibility, and that responsibility should be entirely encapsulated by the class.

That's simple and clear until we start to code. Suppose we have a class with a well defined single responsibility. To serialize the class instances we need to add special atrributes to that class. So now the class has another responsibility. Doesn't that violate the SRP?

Let's see another example - an interface implementation. When we implement an interface we simply add other responsibilities, say disposing of its resources or comparing its instances or whatever.

So my question. Is it possible to strictly keep to SRP? How can it be done?

starsplusplus
  • 1,232
  • 3
  • 19
  • 32
Arseny
  • 7,251
  • 4
  • 37
  • 52
  • 5
    @C. Ross: I think this is concrete enough that it doesn't belong as a Community Wiki entry. – Steven Sudit Jun 08 '10 at 14:25
  • 1
    If you define a class's responsibility as "to do what it's coded to do", then SRP is self-fulfilling. So it really depends on how you define "responsibility". "Implement the characteristics of an X, including serialization, comparison, ..." I think would be a single responsibility. Single responsibility =/= single functionality. – Dax Fohl Mar 30 '16 at 14:49

12 Answers12

82

As you will one day discover, none of the most known principles in software development can be 100% followed.

Programming is often about making compromises - abstract pureness vs. code size vs. speed vs.efficiency.

You just need to learn to find the right balance: not let your application fall into abyss of chaos but not tie yourself hands with multitude of abstraction layers.

  • 10
    Agreed that attempting to follow rules without understanding them is a big mistake. Rules have reasons, and when your context doesn't fit those reasons, you necessarily have an exception. – Steven Sudit Jun 08 '10 at 14:24
  • SOLID perhaps needs another letter in the acronym: P for "Pick your battles" to stress the fact that designs are compromises. There are a couple of good anecdotes in this article: http://www.martinfowler.com/ieeeSoftware/protectedVariation.pdf – Fuhrmanator Aug 06 '14 at 14:12
15

I don't think that being serializable or disposable amounts to multiple responsibilities.

Steven Sudit
  • 19,391
  • 1
  • 51
  • 53
  • 8
    Agreed. And SRP is the most "solid" of the SOLID principles, IMO. The others may bend occasionally, but SRP should never be violated. – Stephen Cleary Jun 08 '10 at 14:05
  • 2
    @Stephen: I think the trick here is to understand what constitutes a single responsibility. In practice, this is going to vary, depending on what is expected of the class, as well as what is required for it to "play nice". – Steven Sudit Jun 08 '10 at 14:23
  • 1
    Depends on what this attribute is doing. It may well add a lot of responsibilities. –  Jun 08 '10 at 14:32
  • well I don't think so. Interfaces or attributes are simply the tools. They extend the behaviour of a class. – Arseny Jun 08 '10 at 14:52
  • 3
    @Developer Art: I would say that adding an interface or attribute *does* change the class, but it doesn't *necessarily* change its *responsibility*. It all depends on what's added. IDisposable, for example, is a matter of GC plumbing, not a feature. On the other hand, if a User class suddenly implemented IEnumerable, that would violate SRP (and common sense). – Steven Sudit Jun 08 '10 at 15:37
10

Well I suppose the first thing to note is that these are just good Software Engineering principles - you have to apply judgment also. So in that sense - no they are not solid (!)

I think the question you asked raises the key point - how do you define the single resposibility that the class should have?

It is important not to get too bogged down on details when defining a responsibility - just because a class does many things in code dosn't mean that it has many responibilities.

However, please do stick with it though. Although it is probably impossible to apply in all cases - it is still better than having a single "God Object" (Anti-Pattern) in your code.

If you are having problems with these I would recommend reading the following:

  • Refactoring - Martin Fowler: Although it is obviously about refactoring, this book is also very helpful in displaying how to decompose problems into their logical parts or resposibilities - which is key to SRP. This book also touches on the other principles - however it does it in a lot less academic way than you may have seen before.

  • Clean Code - Robert Martin: Who better to read than the greatest exponent of the SOLID principles. Seriously, I found this to be a really helpful book in all areas of software craftsmanship - not just the SOLID principles. Like Fowler's book, this book is pitched at all levels of experiance so I would recommend to anyone.

Robben_Ford_Fan_boy
  • 8,494
  • 11
  • 64
  • 85
  • 5
    Clean Code was the book I was reading when I finally had the OO/Testable-code light pop on over my head. The best part of the book is where is cleans up code with a before/after sample. What really struck me was visually how much our code *looked* like the before, it really helped me understand what direction to move the code in for the sake of sanity – STW Jun 08 '10 at 19:35
9

To better understand the SOLID principles you have to understand the problem that they solve:

Object-oriented programming grew out of structured/procedural programming--it added a new organizational system (classes, et al) as well as behaviors (polymorphism, inheritance, composition). This meant that OO was not seperate from structured/procedural, but was a progression, and that developers could do very procedural OO if they wanted.

So... SOLID came around as something of a litmus test to answer the question of "Am I really doing OO, or am I just using procedural objects?" The 5 principles, if followed, means that you are quite far to the OO side of the spectrum. Failing to meet these rules doesn't mean you're not doing OO, but it means its much more structural/procedural OO.

STW
  • 44,917
  • 17
  • 105
  • 161
  • 3
    Or, in other words, you're failing to take advantage of OO. This is bad, not neutral. – Steven Sudit Jun 08 '10 at 15:47
  • @StevenSudit or, depending on context, you're avoiding disadvantages of OO – KolA Aug 06 '19 at 07:30
  • 1
    @KoIA The way you avoid the disadvantages of OO while doing OO is to follow principles like SOLID. Watering down the OO, especially in an unprincipled manner, is full of disadvantages. – Steven Sudit Aug 08 '19 at 13:58
  • 1
    @StevenSudit if you don't want to do OO then you shouldn't use an OOP language. If you're using an OOP language you should understand how to do quality OOP engineering--SOLID aggregates a lot of what the entails. – STW Aug 08 '19 at 14:42
  • @STW I mostly agree. These are just good design principles, applied specifically to OOP. Even if you have legitimate reasons to use C++ in a less OO way, you should stick to the applicable design principles. So, for example, template-heavy code can be less OO, but that doesn't mean it should be bad. – Steven Sudit Aug 08 '19 at 23:38
  • Whoops, I'd meant to reply to @KoIA -- I think we're on the same page. And I agree SOLID is in a lot of ways great engineering principles even outside of OO-specific languages. – STW Aug 08 '19 at 23:46
6

There's a legitimate concern here, as these cross-cutting concerns (serialization, logging, data binding notification, etc.) end up adding implementation to multiple classes that is only there to support some other subsystem. This implementation has to be tested, so the class has definitely been burdened with additional responsibilities.

Aspect-Oriented Programming is one approach that attempts to resolve this issue. A good example in C# is serialization, for which there is a wide range of different attributes for different types of serialization. The idea here is that the class shouldn't implement code that performs serialization, but rather declare how it is to be serialized. Metadata is a very natural place to include details that are important for other subsystems, but not relevant to the testable implementation of a class.

Dan Bryant
  • 27,329
  • 4
  • 56
  • 102
  • That's interesting. I can tell other approach. Remember I read a book about DDD (Domain Driven Design) where author suggests to not write atttibutes but move serialization responsibility to other class. They name it repository. – Arseny Jun 09 '10 at 07:22
  • @Arseny: That's interesting, but I don't see how it would work well in a .NET environment. – Steven Sudit Jun 09 '10 at 16:10
2

So, if instead of designing one "Dog" class with "Bark", "Sleep" and "Eat" methods, I must design "AnimalWhoBarks", "AnimalWhoSleeps", "AnimalWhoEats" classes, etc? Why? How does that make my code any better? How am I supposed to simply implement the fact that my dog will not go to sleep and will bark all night if he hasn't eat?

"Split big classes in smaller ones", is a fine practical advice, but "every object should have a single responsibility" is an absolute OOP dogmatic nonsense.

Imagine the .NET framework was written with SRP in mind. Instead of 40000 classes, you would have millions.

Generalized SRP ("Generalized" is the important word here) is just a crime IMHO. You can't reduce software development to 5 bookish principles.

Simon Mourier
  • 132,049
  • 21
  • 248
  • 298
  • A big problem with the discussions I've seen of SRP is that they fail to distinguish between the public face of a type and its implementation. If code is supposed to model a real-world object which has many abilities, it should create an an instance of a class which exposes public members to access those abilities. If the number of abilities is large, it may be desirable to have the class which exposes those abilities serve mainly as a wrapper for privately-held objects, and chain the outer public members to those of the encapsulated objects. – supercat Dec 26 '14 at 21:57
  • If `MonitorableHeart` and `InjectableBloodStream` are separate classes, code like `while(heart.rate() > 0 && heart.rate() < 70) bloodstream.inject(drugs.someStimulant);` will behave very badly if given a `MonitorableHeart` which is associated with one dog and an `InjectableBloodStream` associated with another. If the "request-heart-rate" and "inject drug" methods both belong to the same `Dog` object, however, that problem goes away. – supercat Dec 26 '14 at 22:03
  • However, what if in the future you'll design several more creatures that can "Sleep", "Eat", or maybe "Bark" You'll probably have a lot of duplicated code. It's better to design the Dog as a composite of his functions, because if you need another type of animal you can create a different composite. As for @supercat's comments, I don't really get it. – Rade_303 Jan 01 '16 at 10:40
  • I never said you should write the code twice. In this case I would create a "Creature" class and the "Dog" would derive from it. Anyway, that's the problem with all those so-called "principes". Nobody gets the same exact idea of it. For a domain called computer *science*, that's a big problem IMHO. Plus I don't know who/why downvoted my answer. – Simon Mourier Jan 01 '16 at 18:37
  • @RockyMM: Suppose one wishes to have a method which tries to maintain a patient's heart rate within a certain range by monitoring the rate and injecting drugs if necessary to bring it into that range. I would posit that it's often much nicer to have that method accept a single reference to an object which supports both a means of querying the rate and injecting drugs, than to have it accept references to independent objects. Such a design may make it necessary to occasionally construct artificial proxy objects which hold references to a heart-monitoring device and an injection pump, but... – supercat Jan 02 '16 at 18:08
  • ...will reduce the risk that the method will receive a reference to a heart monitor that's connected to one patient and an injection pump that's connected to another (which would mean that if the first patient's heart rate goes out of tolerance, drugs would get injected into the second and when those don't work (on the first patient) they'd be followed up with even more. Having an object which couples a patient's heart rate with *that same patient*'s injection pump would reduce that danger. – supercat Jan 02 '16 at 18:11
  • SimonMourier I suggest to you to read https://en.wikipedia.org/wiki/Composition_over_inheritance which is now commonly accepted principle. @supercat How to design the solution to the problem you presented really depends on how the rest of the system looks like. Usually, separating concerns in separate silos (classes) produces more testable and more reusable code. If your concern is that some developer might use _monitorAndInjectIfNeeded_ method differently that its intention, then by all means create multifaceted class. But usually it is overkill - not needed. – Rade_303 Jan 07 '16 at 12:45
  • @RockyMM: The code for monitoring the heart rate and the code for performing the injection should be in separate classes; my point was that I see some significant benefits to having a class which wraps instances of both those classes in a combined object. If there are two objects whose states needs to be kept consistent with each other and they can't practically hold references to each other, it's often good to wrap them both in a common object, and perform all operations upon them through that common object. – supercat Jan 09 '16 at 17:57
2

The thing to remember about design principles is there's always exceptions, and you wont always find that your scenario/implementation matches a given principle 100%.

That being said, adding attributes to the properties isn't really adding any functionality or behavior to the class, assuming your serialization/deserialization code is in some other class. You're just adding information about the structure of your class, so it doesn't seem like a violation of the principle.

wsanville
  • 37,158
  • 8
  • 76
  • 101
2

I think there are many minor, common tasks a class can do without them obscuring the primary responsibility of a class: Serialisation, Logging, Exception Handling, Transaction Handling etc.

It's down to your judgement as to what tasks in your class constitute an actual responsibility in terms of your business/application logic, and what is just plumbing code.

JonoW
  • 14,029
  • 3
  • 33
  • 31
2

To answer your questions directly:

Suppose we have a class with a well defined single responsibility. To serialize the class instances we need to add special attributes to that class. So now the class has another responsibility. Doesn't that violate the SRP?

No, it doesn't violate the Single-Responsibility Principle (SRP).

Let's see another example - an interface implementation. When we implement an interface we simply add other responsibilities, say disposing of its resources or comparing its instances or whatever.

No, these are not necessarily other responsibilities per se. Read below for clarification.

So my question. Is it possible to strictly keep to SRP? How can it be done?

Yes, it should be possible.

Explanation of the why and how:

What is a responsibility? In the words of Robert C. Martin (aka. Uncle Bob) himself:

SRP = A class should have one, and only one, reason to change.

The "single responsibility" and "one reason to change", in SRP, has been a source of much confusion (e.g. Dan North, Boris). Likely since responsibilities of pieces of code can, of course, be arbitrarily conceived and designated at will by the programmer. Plus, reasons/motivations for changing a piece of code may be as multi-faceted as humans are. But what Uncle Bob meant was "business reason".

The SRP was conceived in an enterprise business context where multiple teams have to coordinate work, and it's important that they can work independently most of the time, within their various business subunits (teams/departments etc.). So they don't step on each others toes too much (which also explains SOLID's infatuation with interfaces..), and so that changing code to meet a business request (a business use case, or a typical request from a business department) can be localised and cohesive, making a targeted and isolated change to the code without touching common/global code.

In summary:

SRP means that code should have "one business reason" to change, and have "a single business responsibility".

This is evident from the original source, where Robert C. Martin aka "Uncle Bob" writes (where brackets and emphasis are mine):

Gather together those things that change for the same reason, and separate those things that change for different reasons.

This principle is often known as the single responsibility principle, or SRP. In short, it says that a subsystem, module, class, or even a function, should not have more than one reason to change.

This principle is often known as the single responsibility principle, or SRP. In short, it says that a subsystem, module, class, or even a function, should not have more than one reason to change. The classic example is a class that has methods that deal with business rules, reports, and databases:

public class Employee {
    public Money calculatePay() ...
    public String reportHours() ...
    public void save() ...
}

Some programmers might think that putting these three functions together in the same class is perfectly appropriate. After all, classes are supposed to be collections of functions that operate on common variables. However, the problem is that the three functions change for entirely different reasons. The calculatePay function will change whenever the business rules for calculating pay do. The reportHours function will change whenever someone wants a different format for the report. The save function will change whenever the DBAs [Database Administrators] change the database schema. These three reasons to change combine to make Employee very volatile. It will change for any of those reasons.

Source: Chapter 76. The Single Responsibility Principle (from the book: "97 Things Every Programmer Should Know")

Why was it important to separate these two responsibilities into separate classes? Because each responsibility is an axis of change. When the requirements change, that change will be manifest through a change in responsibility amongst the classes. If a class assumes more than one responsibility, then there will be more than one reason for it to change. If a class has more then one responsibility, then the responsibilities become coupled. Changes to one responsibility may impair or inhibit the class’ ability to meet the others. This kind of coupling leads to fragile designs that break in unexpected ways when changed.

Source: Chapter 9 - Single Responsibility Principle

Much misunderstanding would have been averted if Uncle Bob instead of writing "reasons" had written "business reasons", which was only made clearer when one reads and interprets the fine print later on. Or if people went to the original source of SOLID and read it thoroughly, instead of going by some hearsay version of it online.

SOLID criticisms:

SOLID defense:

Connascence is related to the Single-Responsibility Principle (SRP):

  • Connascence is a measure of coupling and cohesion.

  • connascence.io and Connascence at wikipedia: "In software engineering, two components are connascent if a change in one would require the other to be modified in order to maintain the overall correctness of the system." and "Components that are "born" together will often need to change together over time. Excessive connascence in our software means that the system is hard to change and hard to maintain." from Jim Weirich's youtube talk

PS: SRP can be compared with its closely related sibling, BoundedContext, which has perhaps best been defined as "A specific responsibility enforced by explicit boundaries" by Sam Newman, at 12:38. A lot of these principles are just derivations/restatements of the over-arching important software principle Prefer Cohesion over Coupling. Uncle Bob even introduces SRP by saying: "This principle was described in the work of Tom DeMarco and Meilir Page-Jones. They called it cohesion. As we’ll see in Chapter 21, we have a more specific definition of cohesion at the package level. However, at the class level the definition is similar."

Magne
  • 16,401
  • 10
  • 68
  • 88
1

By changing your definition of "single responsibility" - SOLID principles are quite liquid and (like other catchy acronyms of acronyms) don't mean what they seem to mean.

They can be used as a checklist or cheat sheet, but not as complete guidelines and certainly not learning material.

ima
  • 8,105
  • 3
  • 20
  • 19
  • 1
    "SOLID principles are quite liquid" I like it )) – Arseny Jun 08 '10 at 14:56
  • that's the general problem with SOLID and OOP in general: best practices described so vaguely to be almost useless and when one fails to follow them there is always universal disclaimer "this is because you are doing it wrong" :) – KolA Aug 06 '19 at 07:25
0

SRP is a bit vague term in my view. No one clearly can define what a responsibility supposed to be. The way I implement it s that I strictly keep my method size below 40 and target below 15.

Try to follow common sense and don't be over obsessed with it. Try keep classes below 500 lines and methods below 30 lines at max. That will allow it to fit to a single page. Once this becomes your habit you will notice how easy it's to scale your codebase.

Reference: Uncle Bob Martin in Clean Code

Jeff Schreib
  • 119
  • 1
  • 3
-2

S.O.L.I.D stands for:

  • Single responsibility Principle
  • Open-closed Principle
  • Liskov's substitution Principle
  • Interface segregation Principle
  • Dependency inversion Principle

These are the standards we refer to, when we talk about OOP. However, none of those principles can be fulfilled perfectly in software development.

You can view a very well explaining presentation about this topic here http://www.slideshare.net/jonkruger/advanced-objectorientedsolid-principles