In this section of understanding the SOLID development pattern we are going to walk through the single responsibility principle. While the concept of single responsibility has been around for a while it was popularized in 2003 by Uncle Bob.
Single Responsibility Principle
So what exactly is the single responsibility principle? The term is pretty self descriptive. Essentially it means that each class and module in a program should focus on a single task.
SRP in the Real World
Before we go into a code example, let’s look at the real world case study of a vacuum cleaner. A vacuum cleaner can perform a number of tasks such as cleaning floors and upholstery. Some of the more upscale vacuums can even work like a blower. However notice how, at its core, a vacuum has an engine that utilizes air to perform the task of cleaning?
It wouldn’t make sense for a vacuum cleaner to also wash windows. If you introduced this type of feature to the vacuum it may work for a while, but would most likely cause increased maintenance costs when it would inevitably break down.
Single Responsibility Principle Example in Ruby
Now that you have an idea of how the single responsibility principle works in the real world, let’s dive into a code example.
The Class That Knew Too Much
Here we have an
Invoice class that seems to be relatively straightforward. The class:
- Prints out details about the invoice
- Calculates sales tax
- Emails the invoice with its details
When we run the code everything works fine.
This may seem fine at first, however this code is breaking the single responsibility principle in a number of ways.
Rule of Thumb: No ‘Ands’ Allowed
When it comes to following the SOLID design pattern a good rule of thumb is that if your description of a class has the word
and in it, then it may need to be refactored. For example, let’s describe this class:
The invoice class prints out invoice details AND calculates sales tax AND emails the invoice.
Whenever I’m performing a refactor I like to treat the behavior between the ANDs as their own class.
A Mailer Class
So let’s refactor this code, starting with the email feature. Here we have created a new class called
Mailer that has a single method that will send out an email. The
Sales Tax Class
Next is our sales tax feature. This component definitely shouldn’t be included in the
Invoice class since it doesn’t take much imagination to realize that this feature may be required by other parts of an application outside of the invoice.
Here we’re create a
SalesTax class that takes in the
state we want to generate the sales tax for. Running this code will show that our program is still working perfectly.
And now our
Invoice class is following the single responsibility principle. Notice how the invoice is no longer in charge of generating sales tax or emailing customers?
Why the Single Responsibility Principle is Important
So why is this type of design pattern important? Our initial
Invoice code worked fine, so why would we have to change it? Let’s imagine that this program was used by a real world accounting division. What if a user wanted to see what the tax rate would be for a specific state? It wouldn’t make sense for the system to require the user to create an invoice to calculate that value.
By refactoring the program in the way we did in this guide our
SalesTax class could be used independently or by any other classes that may need the feature. Such as a project estimator class. In the computer science world this concept is called coupling. In our initial code example, the sales tax component was highly coupled to the
Invoice class. This means that it would be messy to work with the tax rate generator without having to also work with the
Our refactor fixed this issue and now we can say that the tax feature has low coupling. Which means that users can access the tax rate component without having to work with other classes.
I hope that this has been a helpful guide to understanding the SOLID element of the single responsibility principle. And good luck with the coding!