Single Responsibility Principle, Object Orientation & Active Code
Jason Folkens had a comment on my previous post:
When people combine methods and data into a class in a way such that you are recommending, I wonder if they truly value the single responsibility principle. In my mind, storing both schema and behavior in the same class qualifies as a violation of the SRP. Do you disagree with me that this is a 'violation', or do you just not think the SRP is important?
I can’t disagree enough. From Wikipedia:
An object contains encapsulated data and procedures grouped together to represent an entity.
The whole point of OOP is to encapsulate both data & behavior. To assume otherwise leads us to stateless functions and isolated DTOs.
Or, in other words, procedures and structures. And I think I’ll leave that to C.
Comments
To add to what Ayende said, Fowler wrote about this subject in an article titled Anemic Domain Model as an anti-pattern. http://martinfowler.com/bliki/AnemicDomainModel.html
I agree. I've seen a lot of this in the last couple of years...The intent of SRP is to simplify code and make it maintainable, extensible,etc....but when you take it out of a particular problem context and apply it ad infinitum...it actually increases the complexity of the codebase...sometimes by orders of magnitude...and naturally creates a serious maintenance disaster.
What a given class does--that is, everything you must know about how to use it, i.e. its contract, i.e. its interface--should generally be a matter of behavior, not data or structure.
In terms of behavior then, anemic domain model and C-style structs adhere to the Zero Responsibility Principle.
Agree... SRP does not mean giving the same responsibility to many parts of your code.
Uncle Bob Martin’s definition of SRP: “There should never be more than one reason for a class to change.” ... and ... “If you can think of more than one motive for changing a class, then that class has more than one responsibility.” (** are my emphasis).
In his video series on Clean Coder, he gives a number of examples of what motivations could possibly exist, including seemingly blasphemous ones like the various departments inside a given organization that may desire a change ... an accounting department's view of a customer (financial) vs marketing (demographics) vs. an executive's view of a customer (aggregate / reporting).
So, when this idea was first introduced, "responsibility" meant a single motivation to change, not a single task or concept necessarily.
Separating code and schema is a common strategy in FP however, Haskell and other functional languages use functions and algebraic data types for this. So it is an anti-pattern in OOP, but common practice in FP.
Now how will you evaluate this in C# which is an OOP language with some functional elements? Are extension methods an anti-pattern? Or Linq to Objects?
Markus, this is exactly the thing why I fail at OCP in FP, how is it done?
So how do you inject dependencies into the objects that are needed for their operations? What if the object is instantiated by a 3rd party component (EF, NHibernate...)?
Do all methods of your object use the same dependencies? If not why not pass an interface / delegate to the method call.
@BOB: I don't think you understand what Uncle Bob meant. If your class does two things, executes two task then there are at least to motivations to change that class. So single responsibility is single task. Not what you say.
I totally agree. The Single Responsibility Principle and other principles are almost always talking about the behavior.
I'm sticking with Uncle Bob's Definition. "Full Fat" domain models tend to go against the Single Responsibility Principle (SRP).
Scott Hanselman & Bob discuss this @3 mins in the following podcast
http://www.hanselminutes.com/145/solid-principles-with-uncle-bob-robert-c-martin
Are you going to resume the excellent design patterns series any time soon? Seem to got stuck at Command pattern for a while now...
I found the series a great help in better understanding of patterns especially from the .net point of view. If you ever decided to publish this in a book form I would be very happy to be your first customer!
I highly recommend this article (and by the way the whole blog):
http://www.carlopescio.com/2012/07/life-without-stupid-objects-episode-1.html
It's not for the TLDR kind of people.
@Jiggaboo … I think we're missing each other on the semantics of the term "tasks" -- it seems to mean different things to each of us, however I think (hope) we're talking about the same thing in general. I would say this. Listen to the link @PaulRMortimer includes in his post (above) from Uncle Bob … that's how I understand SRP. Would you agree with Uncle Bob's description of SRP from that excerpt or not?
"The whole point of OOP is to encapsulate both data & behavior"
That's a surprising statement considering how much time you have spent disagreeing with it over the years. The active record pattern, for instance, encapsulates data and behavior.
"To assume otherwise leads us to stateless functions and isolated DTOs."
Isn't this exactly what you've been advocating for? IE, your articles on command/query architectures end up exactly this way. A query (dumb), is executed by another class method (stateless) and returns a model (dumb).
The "enterprise" app I'm working on at the moment is a mess of spaghetti code precisely because it mixes data and behavior. eg:
var dto = new CompanyDTO(); dto.Load(56); //load company string s = dto.Name; //Name is set in the load function
Flukus, Most of the time, you want to have data in the object, and NOT pass around DTOs. Sure, CompanyDTO is wrong, but not because it has data & behavior. Because it has the wrong behavior. Company (not DTO) should have the company data and company behavior (for example, payment policy, etc). Loading the data is not a related behavior, can & should be external.
In a sense I understand what Jason Folkens meant. With the experience over the years, I favor immutable data objects, and pure controllers (i.e classes with no state and no side effect) . Immutable data objects contain contracts. They sometime also contain side effect free behaviors (like ToString() for example) but it is not a rule (not an exception).
Doing so doesn't mean I left aside encapsulation, interfaces and inheritance, hence I am not a C programmer :)
But doing so makes the code highly testable and much less error prone.
I have to agree with Patrick, data objects and stateless controllers are a far cry from traditional C apps.
Additionally, I'm not seeing the disadvantages I would expect from something deemed an "anti-pattern". The main argument seems to be that this style is not theoretically pure...not an argument I'm sympathetic to.
This seems a little above my head, but it looks to me as if the argument is whether or not data-storage models should contain any of the logic pertaining to the operations they perform. Is that anywhere in the right ballpark?
Well, that being an object representing a identity, state and behavior..
Misconception occurs when you slice and dice and SRP isn't maintained..
Company should represent company only.. Loading, etc has nothing to do with Company itself..
I often see this violated, typically by inheritance, and not cutting the cake correctly.
I think what Ayende is trying to convey is to apply some common sense when talking about SRP. Logically yes, you could make the argument that any kind of behavior in a class violates SRP, but that's taking it a bit extreme.
SRP states that UNRELATED behavior should be in its own class. For instance, ActiveRecord violates SRP because data access has nothing to do with whatever you're modeling (let's say a Product class for simplicity's sake). On the other hand if your Product class had a bit of logic to calculate its own discount or total (e.g. price * qty), that does not violate SRP because that sort of behavior is related to the Product class.
It seems very few developers actually understand object oriented development. I've not seen it applied at my workplaces (.NET) since I left the C++ world. My theory is that combining object oriented development with storing those objects in a relational database is what has been messing things up. I'd like to say that it is not viable to do object oriented development and at the same time store those entities in a relational database (i.e. do o/r mapping). Of course, as you would agree, this is where RavenDB fits in. I guess that's a topic for many other posts :-)
I believe that the SRP leaves many things unexplained. I believe the idea of SRP was good. But the phrasing and communicating leaves so many things open to interpretation (what constitutes a responsiblity?). There's always discussion about it, and half the time people thinking they've understood probably didn't. I would vote to strike SRP from all books and replace it by a more goal oriented description*. We want maintainable, extensible and testable code. Using a test-driven approach we probably pretty soon (in a matter of weeks or months) learn how to achieve simple tests. Given that this is probably the single most important thing about OO development, it should be worth the time, shouldn't it?
*please note that i did not give a complete goal oriented description. I admit it's a lot of speculation without something to compare...
Bruno, I think you should see SRP as a guideline. Just like 'long methods are bad' is a guideline.
In both cases you wage full blown wars over the exact definition. Or you can just keep it in mind while developing and let your judgement decide whether or not you violated it in every situation.
@Patrick Huizinga, @Rob
I have a similar opinion to you guys and I'm doing a presentation about it.
Wanna review my slides? https://github.com/NTCoding/Presentations/tree/master/SOLID_is_a_guideline
Yes, I too consider SRP being a guideline..
What I've learned through the years is to incorporate it into your mindset, and not just as a checklist, looking at "what is best practice"..
And I agree that OO in general often is misunderstood as a whole. How to model your objects, communicate, making them lightweight and testable.
Quick example is a junior developer I've worked with, doing a huge class hierachy, with validation and buisness rules, that combined with ORM state and behaviour..
So what I do, is that I ask the Question.. Looking at the real world (and we try to convey, the real world)... When doing simple OO, when modelling, somekind of entity.. When posting that mail in the mailbox, does it send itself? Or do we need a mailman to handle this?
I've had great succes with that notion of looking at objects as objects in the real world - talking to juniors.
The encapsulation of data and then methods that work on that data (traditional OOP definition of a class) has nothing to do with SRP. SRP ensures that data and methods encapsulated within that class/object have a single purpose - cohesiveness.
Don't make things complex. It's very easy. Every object provides some public interface (which abstracts details) and might have PRIVATE, hidden state which dictates it's behavior. All methods modifying or reading private state should belong to the object, otherwise you will have to reveal the internal state and loose the benefit of abstraction. Everything else that uses abstract contract (operates on public interface) should be moved away. The reason for this is that if you put it together it makes your code much less flexible. For example you have class Customer with property Name. If you put logic of validation of this property or logic of storage of this property into Customer itself you have limited yourself to having only one possible validation per customer. Now consider situation when you have different rules of what makes Customer name valid dependent on context. With regards to SRP I agree to jmorris. SRP states just what high cohesion requires.
Comment preview