I've always considered the single responsbility principle more of a philosophy than principle.
Robert C. Martin (Uncle Bob) restates the Single Responsibility Principle (linked to PDF):
There should never be more than one reason for a class to change
Quoted from the reply you got your post.
When we try to appease the SPR, we focus on the implementation rather than on the purpose.
Major mistake.
My understanding of the SRP is this, when an addition blurs the responsibility of an implementation, the focal point should be reconsidered.
For me, it is all about the relationships between purposes.
Sure, a user has to be authenticated and authorized regarding actions to take - but the authentication and authorization should be separated from the user.
It's not like you are going to try to authorize/authenticate a non-user anyway?
And thus, you should consider splitting implementation when the responsibilities of a class outgrows it purpose. After all, design patterns have purposes too.
Take a look at GRASP when you get a chance.
Behavior in this context is refering to semantic behavior. For example, a Property on a class (i.e. attribute on a domain object) that is used to uniquely identify it has a behavior. While this is not represented in code directly. The expected behavior is that there will not be any duplicate values for that property. Something such as an Address which may have its own identity, but does not exist outside of the context of a Person Entity should still be moved out into it's own object. Thus promoting the Entity into an Aggregate Root.
Furthmore, a property such as Age has no context outside of a Person Entity, and as such, makes no sense to move into a different object. The context would be lost, and therefore you can safely determine that it is a value that is essential to the Person Entity. You could not locate the value otherwise. However that information could easily be stored in a separate location that the unique identifier, hence the confusing reference to behavior. Age could be a lazy loaded value.
So to answer your question. No, it does not violate the Single Responsibility Principle. It is mearly stating that a Person should have only person stuff, and not something like Address, which is more complex and related to a person, should exist as its own entity.
You could easily have operations specific to Address such as verification that it is a valid address. You may not know that at design time, but this whole concept is to break down objects into their smallest parts so that something like this is relatively simple when done after the fact.
Update:
1) In most cases this identity validation is done upon saving an object out to a data store. Which means that the code representing the entity validation exists, but it exists elsewhere. It usually exists with the code that is responsible for issuing the identity value. Which is why I state that the uniqueness is not represented directly in the code for the entity.
2) The correct statement would be that Age
is an attribute that has behavior. You would need to document the fact that Age is lazy loaded so that a developer consuming that property can make an accurate decision on how to consume that property
3) DateOfBirth
usually is a different object; A date object that already has predefined operations on it. In some languages the date object already has a whole domain model defined on it. For example in c# you can specifiy the timezone, if the date is UTC, add and subtract dates to get a timespan. So your assumption about moving DateOfBirth
would be correct.
4) If the only thing that MyEntity
does is delegation and cooridination then no it does not violate SRP. Its single responsibility is to delegate and coordinate and is refered to as the Facade pattern
Best Answer
One way to wrap your head around this is to imagine potential requirements changes in future projects and ask yourself what you will need to do to make them happen.
For example:
Or:
The idea is to minimize the footprint of future potential changes, restricting code modifications to one area of code per area of change.
At the very minimum, your classes should separate logical concerns from physical concerns. A great set of examples can be found in the
System.IO
namespace: there we can find a various kinds of physical streams (e.g.FileStream
,MemoryStream
, orNetworkStream
) and various readers and writers (BinaryWriter
,TextWriter
) that work on a logical level. By separating them this way, we avoid combinatoric explosion: instead of needingFileStreamTextWriter
,FileStreamBinaryWriter
,NetworkStreamTextWriter
,NetworkStreamBinaryWriter
,MemoryStreamTextWriter
, andMemoryStreamBinaryWriter
, you just hook up the writer and the stream and you can have what you want. Then later on we can add, say, anXmlWriter
, without needing to re-implement it for memory, file, and network separately.