Software engineering is full of patterns and principles to help you get the job done. Sometimes those patterns and principles are defined very abstract and difficult to understand. Let’s talk about SOLID design principles in a 5 part series. Today we’re discussing the Single Responsibility Principle.
I recently started a new job. As part of onboarding, I had the opportunity to brush up on important concepts. One recommendation given was a PluralSight course for employees that are not “solid on SOLID”.
While I know and have been practicing SOLID principles I still felt like this advice is sound. What occurred to me as I watched the course is that while many of us developers — especially seasoned ones — feel like we know and understand concepts, it is never a bad thing to reinforce that knowledge. Throughout this series I’m going to try and boil down the concepts to a simple and concrete form.
What is SOLID?
First off, let’s introduce the concept of SOLID. SOLID is an acronym for five design principles intended to make our software understandable, flexible, and maintainable. The ideas were conceptualized by Robert C. Martin aka. Uncle Bob in Design Principles and Design Patterns paper in 2000. [available here]. Following these principles will help you build good (maintainable) software.
The five principles of SOLID are: Single Responsibility Principle (SRP), Open-closed principle, Liskov substitution principle, Interface segregation principle, and Dependency inversion principle.
What is the Single Responsibility Principle?
The Single Responsibility Principle is simply defined “A class should have only one reason to change.” Uncle Bob coined this in his book Agile Software Development, Principles, Patterns, and Practices. He also wrote a blog post about it.
Now, if you’re like me then you’re probably saying “um… ok… what does that mean?” Uncle Bob himself poses that same question in the aforementioned blog post. What constitutes a reason to change? He boils it down to ultimately the responsibility of the business logic. There should only be one business reason to change the logic in that class.
In a webcast with Rob Conery, Jon Skeet made a couple interesting comments. By the way, this webcast was later converted into a PluralSight course titled C# Design Strategies. Specifically he stated that “part of this is being completely clear about what the class does”. He then points out how the DateTime class in the .NET framework violates SRP. The violation is because it does several things. This ultimately causes confusion.
Another comment he made was: “if you thoroughly understand the business needs of your model, creating classes with single responsibilities and clear meaning becomes easier.”
Two more comments made during that discussion are: “… increase cohesion between things that change for the same reason, decrease coupling between things that change for a different reason.” and “do one thing and do that one thing well.”
Basic Example
Let me jump back to an example Uncle Bob gave in that aforementioned blog post. He points to a typical business organization and the reporting structure. He then points at some example code for an Employee
class. It had a few different methods on the employee: calculatePay
, save
, and reportHours
. He goes on to ask who is responsible for the behavior of those methods. By understanding who within the organization is responsible for that behavior, he helps point out that the code also needs to be segregated out by that responsibility.
What Single Responsibility Principle is not
We all know that ubiquitous business logic object. You know, that monolithic one. The on that has hundreds maybe even thousands of lines of code. That thing does everything. You know what one I’m talking about, right? Yeah, we’ve all been in a codebase with it. Chances are most of us are probably the ones who wrote it. So that violates SRP something fierce. Why though?
For starters… it does everything. I need to perform [this operation] on the code, it’s there. You want to perform [that operation], it’s there as well. See what I mean? It has more than one purpose.
That of course is on one extreme and is pretty blatantly obvious. How about we look at the flipside though. If I have a database repository, would it be in line with SRP if I make each function it’s own class? What I mean is, maybe I have a class for SaveEmployee
and another for LoadEmployee
. Hopefully you’re seeing what I mean. The responsibility of that repository is to interact with the DB for the Employee. Surely it is ok if it knows how to both save and load employees. That’s it’s responsibility.
Let’s continue on that path. I’m making modifications to my Employees
table. If my repository is split into multiple classes I have some problems. First, I’ve just made my life harder because I have to modify multiple areas. Second, there is no real cohesiveness between them. A repository makes sense lumping all those into a single class because again, that is it’s responsibility.
The bottom line here is that when you’re learning SRP you might be tempted to divide things too far. Don’t.
Real World Example (of what not to do)
Let’s say you have a cart/checkout process. In our monolithic programming days the Cart might be represented by this interface:
public interface ICart
{
CartItem[] Items { get; }
PaymentItem[] Payments { get; }
ValidationResponse ValidateCart();
SavePaymentResponse SavePayment(SavePaymentRequest request);
DeletePaymentResponse DeletePayment(DeletePaymentRequest request);
SaveItemResponse AddItem(CartItemRequest request);
DeleteItemResponse DeleteItem(DeleteItemRequest request);
ProcessCartResponse Process();
CartModel ToModel();
}
For sake of this argument let’s pretend that the class that implements this interface has all the code inline to perform these operations.
This object knows a lot about a lot. It knows about it’s items and payments. It knows how to add and remove payments. The object knows how to add and remove items. Processing the cart is even a function it is in charge of. Lastly, it knows how to convert itself to a model representation, likely to send across an API.
How would you go about decomposing this monolith?
Applying the Single Responsibility Principle
Looking at our ICart
, let’s see what we can do apply SRP. Should a cart know how to add/remove payments? Is it ok for that cart to know how to add/remove items as well? What about validation? How about processing? Conversion between representations? How far do we go?
Let’s start with what ICart
should likely represent in our system. Perhaps it’s sole responsibility is as a DTO. If that were the case then it makes sense for it only to have the properties for Items
and Payments
. It would likely have a few other properties as well, but let’s just stick to our example.
Moving forward, then, I might have an IPaymentList
. The IPaymentList
would know about it’s own payment instances. It would also have the ability to add and remove payments from itself.
Similarly, perhaps I have an IItemList
. Just like IPaymentList
, it would contain it’s own item instances and the ability to add or remove items.
For validation we probably have an IValidateCart
. It will take an ICart
in and produce some sort of validation response as output.
Processing the cart would be quite similar to our validation process. We would simply have an IProcessCart
. ICart
is sent in as input, and some sort of process response as output.
Converting the ICart
to a different form, again, is similar. Let’s step aside for a quick moment to discuss why you might be doing this. If ICart
is a DTO then it should be safe to transfer as-is, right? Well sure. But what if we need to compose other information on top for that conversion? One example is content localization (messages for the cart, etc).
So that in mind, maybe I have an ICartToCartModelAdapter
. Naming is hard. Is it an Adapter or is it a Converter? That’s a topic all on it’s own. I’m going to call it an Adapter. Sue me.
That’s all fine and dandy… but now what?
I’ve now taken one interface and split it out to four new pieces of code. They all have their own responsibility but there really isn’t anything gluing them together anymore right? We’ve a couple different ways to approach this and it depends on how you get to this information.
In the context of a web application I’m probably not saving payments and adding items at the same time. Similarly I’m not likely saving payments and processing at the same time. Most likely I have some sort of API that I call into and perform operations one at a time. Let’s expand on that idea just a tad.
From my web interface I click “Add Payment”. It presents me a form whereupon I fill in my payment information. I proceed to click “save” and it fires off an event. That event passes along and generates a JSON post to my web API. The API now needs to do a couple things. It has to validate that the information posted is enough and it has to persist that payment to my cart.
Aggregate Responsibilities
Enter ISavePaymentCommand
. The purpose of this command is to validate and save the payment. It, by itself, probably shouldn’t know how to do either of those. Instead it is aware of objects that know how to do those things independently and it aggregates the results. Does this violate SRP? No. Do you know why? I can independently change the validation or payment handling routines and they shouldn’t directly affect this class. Why? Because their inputs and outputs should remain the same. The concrete for this interface might look like this:
public class SavePaymentCommand
{
public SavePaymentCommand(IPaymentList paymentList, IPaymentValidator paymentValidator)
{
PaymentList = paymentList;
PaymentValidator = paymentValidator;
}
protected IPaymentList PaymentList { get; }
protected IPaymentValidator PaymentValidator { get; }
protected IPaymentRequest PaymentRequest { get; private set; }
// any sort of initialization specific to this; probably needs to seed the IPaymentList and IPaymentValidator stuff
public void Initialize(IPaymentRequest paymentRequest)
{
PaymentRequest = paymentRequest;
// pass whatever information necessary (if any) to initialize this
PaymentList.Initialize();
PaymentValidator.Initialize(paymentRequest);
}
public SavePaymentResponse SavePayment()
{
var validation = PaymentValidator.Validate();
IPaymentItem newPaymentItem = null;
if (validation.IsValid)
newPaymentItem = PaymentList.SavePayment(PaymentRequest);
// add whatever information to the response you need/want
return new SavePaymentResponse();
}
}
Identifying Violations of SRP
If your class functionality can be described as does this “and” that, it might violate SRP. Similarly if your class functionality can be described as does this “or” that, it might violate SRP. Might. Look at our ISavePaymentCommand
. It’s purpose is to do this “and” that but it does so by forwarding that functionality elsewhere.
If your class or method is large, it might violate SRP. What is defined as large? Some people go to the extreme and say if the class has more than one method, it is large. I refer you back to a repository. That might be large but it also wouldn’t necessarily violate SRP.
How do you decide if a method is large? Some people might tell you that a cyclomatic complexity level in excess of [insert value here] is too large. I’m not going to tell you what is too big or how to calculate it. I will say, however, that cyclomatic complexity does help and in my own use-cases I try to keep them below 100.
If you have to write verbose comments to describe functionality of a class or method, it might violate SRP. A class or method should be completely clear about what it does therefore commented documentation shouldn’t be necessary. Did I just tell you not to write comments? No, I absolutely did not. If you do write comments, however, that is now as much part of your code as the code is. Thus, changes to the code should also require changes to the comments.
If your class or method is too hard to unit test, it might violate SRP. What I refer to here is if it has too many dependencies that require a lot of setup. Each dependency should be independently testable. You should be able to “mock” functionality on those dependencies so you are concerned solely with the functionality within your class or method.
How to refactor into SRP
I gave a fairly basic yet totally believable example of something you could run into. I started with a monolithic object that did too much. In order to break it down, I first identified the different responsibilities of that code. Next I labeled those responsibilities (Payment Listing, Item Listing, Cart Validation, Cart Processing, and conversion to another form).
Once I had identified my responsibilities and labeled them, I was ready to break them into single responsibilities based on those labels. Ultimately doing so, however, identified to me some new responsibilities such as aggregating responsibilities. I ended up with many more classes but, except for the aggregates, they are all independent.
The Strangler Pattern
Another way to look at this is by applying the Strangler Pattern. Generally speaking you’ll see this talked about in the context of migrating a monolithic application to microservices. Guess what though? It is just as applicable at the code level as it is the application level. I invite you to read this post by Michiel Rook. He talks about applying the stranger pattern within your application. While he also is still talking about it at an application level I want to point out his bullet points.
- Complicated, legacy code
- Fragmented business rules
- Monolithic design
- Hard to bring under test
- Lots of technical debt
- Painful deployment process with lots of manual steps
Additionally a comment he made was that “The problems were so great that the team did not have much confidence in modifying the existing code, resulting in low velocity and increased lead or cycle times.”
For brevity sake the premise of the Strangler Pattern is that you will take single pieces of functionality, extract them out into their own modules, and replace their reference in the original. You are “strangling away” the functionality. Eventually you’ll have killed off the original host.
Don’t Repeat Yourself
I want to bring up another concept that pairs well with SRP. Don’t Repeat Yourself (DRY) is “a principle of software development aimed at reducing repetition of software patterns, replacing it with abstractions or using data normalization to avoid redundancy.” (see Wikipedia entry, see also The DRY Principle Explained)
Given that the single responsibility principle has the purpose of boiling our code down to individual jobs, SRP can be considered an implementation of DRY. The inverse is not necessarily true. DRY code can exist outside of SOLID principles.
Conclusion
SOLID design principles help us achieve maintainable software. The first principle of SOLID is the Single Responsibility Principle. This principle states that a class should have only one reason to change. It is easy for us to take this out of context, however. We need to understand the business reasons for a class or method to exist. By understanding those reasons we can construct cohesive objects that change for the same reason.
Credits
Photo lifted from KnowYourMeme.com