PTOM: The Single Responsibility Principle

After Chad and Ray I followed suit as well and am doing Pablo’s Topic of the month post on the Single Responsibility Principle or SRP for short.

In SRP a reason to change is defined as a responsibility, therefore SRP states, “An object should have only one reason to change”. If an object has more than one reason to change then it has more than one responsilibity and is in violation of SRP. An object should have one and only one reason to change.

Let’s look at an example. In the example below I have a BankAccount class that has a couple of methods:

   1: public abstract class BankAccount
   2: {
   3:     double Balance { get; }
   4:     void Deposit(double amount) {}
   5:     void Withdraw(double amount) {}
   6:     void AddInterest(double amount) {}
   7:     void Transfer(double amount, IBankAccount toAccount) {}
   8: }

Let’s say that we use this BankAccount class for a persons Checking and Savings account. That would cause this class to have more than two reasons to change. This is because Checking accounts do not have interest added to them and only Savings accounts have interest added to them on a monthly basis or however the bank calculates it.

Some people may say that the class would even have 3 reasons to change because of the Deposit/Withdraw methods as well but I think you can definately get a little crazy with SRP. That being said, I believe it just depends on the context.

So, let’s refactor this to be more SRP friendly.

   1: public abstract class BankAccount
   2: {
   3:     double Balance { get; }
   4:     void Deposit(double amount);
   5:     void Withdraw(double amount);    
   6:     void Transfer(double amount, IBankAccount toAccount);
   7: }
   9: public class CheckingAccount : BankAccount
  10: {
  11: }
  13: public class SavingsAccount : BankAccount
  14: {
  15:     public void AddInterest(double amount);
  16: }

So what we have done is simply create an abstract class out of BankAccount and then created a concrete CheckingAccount and SavingsAccount classes so that we can isolate the methods that are causing more than one reason to change.

When you actually think about it, every single class in the .Net Framework is violating SRP all of the time. The GetHashCode() and ToString() methods are causing more than one reason to change, although you could say that these methods are exempt because they exist in the framework itself and out of our reach for change.

I’m sure you can come up with a lot more instances where you have violated SRP, and even instances where it just depends on the context. As stated on Object Mentor: “The SRP is one of the simplest of the principle, and one of the hardest to get right”.

Here is a link to the SRP pdf on Object Mentor for more information.

About Sean Chambers

I am a Senior software developer from Palm Coast, Florida. An advocate of Domain Driven Design, Behavior Driven Development, creator of FluentMigrator and community activist. I am married to my beautiful wife Erin and am the proud father of two wonderful children. I currently reside at ACI, a local insurance industry/mortgage software company that excels in creating solutions using Agile methodologies.
This entry was posted in PTOM. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Interesting, I’ve never seen the SRP that way before.

    I guess though that this is really just handling implementation SRP because you’ve separated the code (though into a subclass rather than a totally separate class) but you’ve not got interface SRP. In this case that’s fine but in a lot of cases I think you really need to break the behavior out completely.

    Just in general my favourite descriptions of SRP comes in “Working Effectively With Legacy Code”, I particularly like the ScheduledJob example.

  • dauchande

    Huh, I see GetHashCode, ToString, Equals, etc in object to be aspects related to the identity of an object, so I don’t think they come under the context of the SRP any more than logging or threading would.

    I see SRP related to the “business” responsiblities of the class and not aspects of it’s identity, information hiding mechanisms, or state management (such as serialization). Although I can see an argument for using a momento to do serialization, but that’s a limitation of the language (platform) and not the domain.

    Now if the BankAccount class did something like saving to the database or managing transactions, then it would have more than one reason to change and I think that would fall under the SRP.

    Well, my $0.02

  • @dauchande i agree with you there. i see it as the same way. only pertaining to the business responsibilities of the class. So I agree with you there.

  • Victor Moran

    The 1st problem I see is the acceptance of an interface (“IBankAccount”) as a parameter; naughty boy (ha!). Are you expecting anything other than a bank account to implement this interface? If not, no need for the extra complexity. You want a bank account (type that “acts like” a bank account), not just something that “looks like” a bank account on the surface – especially in this sensitive context. This is the danger of assuming interfaces specify contracts – that the object behind the interface will be in the proper functional/semantic context expected by the client (THAT’S the difference between “contract” and mere syntatic resemblance). Conversely, there’s no way to know for sure that your overriding functionality isn’t malicious either, but it’s a safer bet (and more under your control).

    The realization that not all bank accounts pay interest is the 1st thing that shoulda happened when modeling “BankAccount”; then would follow the move from the general to the specific. Figuring this out later is OK I guess, but is really just determining that “CheckingAccount” doesn’t have that feature.

    “Colin Jack”s comments got me thinkng, but then I realized that responsibility separation doesn’t have to mean “outside the fam”, so inheritance looks like a reasonable way to breal out responsibility. He has a point though on the whole “implementation vs interface” thing. Actually this is a bad example because if anyone were to look at this class diagram, they’d ask why “CheckingAccount” has no implementation (“class proliferation” problem). From this look (if the names weren’t attached), you’d logically conclude that a savings account is just a specialized checking account. This represents an impedance mismatch between technical and business worlds. In the “real world”, savings accounts came 1st, then checking accounts, which in business seems normal, but plays havoc on the OO-minded because the newer thing should extend capabilities, not taking them away (like the ability to acrue interest going away). Designers would then have to flipSome checking accounts are indeed interest-baring as well so leaving it there would help their refactoring down the road, but just an example of how the example could lead to confusion about the problem, especially when the questionable things centr around what the demo is aluuding to (responsibility in this case).

    Anyway (whew!), I think the problem people have with adhering to this seemingly simple principle stems from the another impedance mismatch between responsibilities and reasons to change. There has to be some kind of “apples-to-apples” endeavor to get on equal terms before you can even compare the 2. Reasons to change can be anything; in this case, the “BankAccount” class’s “Deposit” or it’s “Withdraw” truly represent to distinct functions (and hence, reasons to change. If I change the “Withdraw” ‘responsibility’, it affects “BankAccount” ‘clients’ (like my wife) who only care about this activity – which is OK (nothing broken yet), but it also affects those only interested in making deposits (like my company’s automatic salary deposit system) because this class’s code has been cracked open (possibily introducing subtle bugs to other than the target code) and now has to be regression tested, redeployed, etc., which affects all clients of the class. Now we’ve violated the defined intention of SRP, which is to avoid this by the isolation of responsibilities/reasons to change. I have 2 “reasons to change” but still only 1 stated responsibility to “manage the account”.

    By the same token, the granularity of the responsibility of “managing a bank account” that’s being encapsulated/represented by “BankAccount” is really relative, isn’t it? Withdrawing and depositing at this granularity level, represent 2 entirely different concerns; under this interpretation, does this mean that “BankAccount” is no longer singly responsible/should be broken out? If the responsibility is interpreted as “all things that happen to a bank account”, then we’re OK, and changes to either “Deposit” and “Withdraw” functionality can be ‘interpreted’ as non-disruptive beacuse they combine to represent a single resposibility.

    In the extreme, strict adherence to this principle would mean that each method would be in a class by itself because technically/depending on your altitude/distance from the abstraction, they can be seen as separate responsibilities and reasons to change. Concersely, another extremity is you can move further away and say that an entire system has a single responsibility and should all be in 1 class!

    I think something that helps reduce this impedance mismatch (in conjunction with thinking about
    ‘reasons to change”) – as alluded to above – is focusing on:
    - Data shared between methods
    - The impact to the bject’s client/user of making a change.
    - Usage scenarios
    W/ respect to the “BankAccount” model: that 1st extreme case (all methods in a separate class) provides maximum isolation of the client from change, but results in loss of encapsulation because multiple classes are operating on the bank account. In the 2nd extreme case (all methods in 1 class), this solves the encapsulation problem, but potentially affects every single client using the system when things change.

    We have to come up with “reasonable” solutions, so thinking about all 4 things (reasons to change, shared data, client impacts, usage scenarios) seems a more noteworthy voice of “reason”. Applying these, you seem to end up not with separate “Withdrawl” and “Deposit” classes or a big “Bank” class (e.g. w/ “Deposit” method that also takes the account number), but 2 (parent-child) account classes that seems to reasonably satisfy SRP.

  • @Victor, interesting points. You could have written a whole post on that alone.

    The whole reason that checkingaccount has no implementation is because this is an example. I’m sure if I would have been writing actual classes here everything would have ended up much different.

    The whole reason that I used this was to keep it as simple as possible while still illustrating my point.

    Excellent points however.

  • Victor Moran

    Thanks Sean; yeah, I figured your intentions were to make it easier on the example (shoulda mentioned that).

    This got me reading your other blog posts; pretty good stuff. I enjoyed the “Open Source” thing – although the comments sorta strayed into the weeds there toward the end. Were you thanking “Joe Healy” (the “Fish”)? If ya see him, tell him Vic from The Fund says “hey”!

    Anyway I’m basically in agreement with you though, and will probably add some stuff like I posted on Mats Helander’s blog.

    BTW, do you appear regularly at ONETUG?

    Kinda off-topic, so apologies for taking up the space…

  • @Vic

    Not really. I am in palm coast so ONETUG is about an hour and a half drive. With the recent rise in gas prices I find it hard to make the trek.

    JAXDUG is a lot closer to me and I have done one presentation there last month. I’ll probably go back soon and do some more presentations on more OSS.

    I was in orlando for the code camp recently. That was really neat.