This content originally appeared on Bits and Pieces - Medium and was authored by Noor Ahmed
SOLID principles are your guidebook for developing well-designed systems.
These principles allow you to design maintainable, expandable, and easy-to-comprehend applications. Without these, your code may become rigid and fragile. Any little modifications to the programme might lead to bugs.
“S” in the SOLID principles stands for Single Responsibility Principle, which will be this article’s main topic of discussion.
The problem is that we are typically taught these principles in schools or universities with examples of vehicles or square/rectangle and imprecise terminology, with no apparent pattern to detect the necessity for SRP.
In this article, I will go beyond this and explain SRP in a more practical approach by teaching you the pattern to spot the violation and necessity for SRP, along with appropriate examples. So don’t leave anything out and enjoy reading :)
The Problem with Internet definitions
The most common definition of SRP you will find is:
Each class in your application should have only one responsibility
This definition may appear rational given the name of this concept, yet it begs the question of what truly defines responsibility. And however, there is no clear definition of the term “responsibility,” which is a major problem because it would be practically impossible to reason about distinct responsibilities otherwise.
For example, different developers might have wildly varied ideas about what constitutes responsibility and then discover completely distinct responsibilities within the same piece of code.
Another hazy definition you’ll come across on the internet is that:
You should be able to describe each class without using the word “and”
Let’s have a look at this code.
If I apply the first definition, I would say that this class has two distinct responsibilities: “createMirrorImage” and “WatermarkImage,” thus violating SRP, which is clearly unreasonable given that the entire purpose of this class is to manipulate images.
According to the second definition, “this class creates mirror images and watermarks.” Because the description contains the word “and,” I am violating the Single Responsibility Principle?. But if I define it as “this class is for manipulating images” without the word “and,” it no longer violates??.
This is absurd because I can always describe a class without using the word “and”; thus, none of my classes in my app would violate the SRP!.
Therefore, these two definitions are impracticable.
The Practical Definition:
The most applicable definition of SRP would be:
A class should have one, and only one, reason to change. — Robert C. Martin
This definition advocates for each class to perform only one duty. It does not imply that a class should only have one method or property; rather, these methods/properties should be closely related to the responsibility of a class. A class with numerous objectives should be split into multiple classes.
Let’s revisit the first example briefly:
Using the third definition, I don’t see any reason to change this class because these two functions work towards the same goal: image manipulation. Hence, it does not violate SRP.
Let’s put the “reasons to change” concept into practice by looking at three examples from different settings.
Example 1: The unclear purpose of a class
By looking at it, I can’t determine what the sole goal of this class is. It appears to serve unconnected purposes.
Let’s look for “reasons to change”:
- New demand to add a different invoice type: generateInvoice().
- Asked to change the email template based on the reservation type: sendCustomerEmail().
- Requested to check the cache first when finding an existing reservation: findExistingReservation().
Although I can think of many “reasons to change,” the above appears reasonable, and because I have more than one reason, it violates SRP. It will almost certainly expand the “Reservation” class, which is challenging to maintain and results in tight coupling.
Two concepts that go hand in hand with SRP are:
Cohesion — refers to how tightly everything is tied to one another.
Coupling — is the process through which everything is linked together.
Subsequently, this class is tightly coupled and has low cohesion.
The goal is to achieve Loose coupling and higher cohesion, which aid in achieving greater adhesion to SRP.
Let’s restructure it by classifying these three responsibilities and updating the Reservation class to simply provide id and booking status.
Example 2: Clear purpose of the class
This class appears to have only one purpose: managing agent authentication. However, this class may violate SRP. Before you continue reading, can you think of any reasons to change?
Let’s look at the “reasons to change” likely to happen:
- This class hash password method is private. And if I want to alter the hash algorithm in the future, I’ll have to modify this class.
- It also checks to see if an agent exists in the database. If there are changes to how we determine an agent, or if we need to tweak database setups or read from another DB, we would need to modify this class.
SRP is violated since I have more than one reasons to change. And I can argue that my class is more tightly coupled. However, it is already highly cohesive because they all contribute to a single well-defined task (authenticating the agent).
So let’s loosely couple it by delegating tasks to its own class.
This class now has two private fields, “agentDao” and “hashHelper,” which provide data retrieval and hash functionality, respectively. As a result, it is loosely coupled and does not violate SRP.
Aren’t there more reasons to change in AgentAuthManager class?
You may argue that if we want to send an email upon login, will we not then modify the login function in this class and thus violate the SRP??
The answer is yes! That is possible! Although you may indeed think of numerous reasons to change, nevertheless, we must behave pragmatically and only consider changes that are most likely to occur. Otherwise, you risk over-engineering and wasting time reworking when it isn’t essential. Remember that you cannot account for every conceivable future circumstance while building an application.
Example 3: A new feature request
Assume you are requested to develop an itinerary feature for the trip management application your organization is developing in which you must connect multiple third-party APIs from hotels and airlines, accept payment from various vendors, and save reservations in the MySQL database. However, cloud database support may be introduced later.
So, without the Single Responsibility principle, this is what it would look like:
These APIs integrations and methods to save reservations and receive payments will be used within the Itinerary class. As a result, this class will likely grow and become difficult to manage.
let’s look at the reasons to change:
- Request to support a new hotel or flight — new API integration or a feature.
- Integrate a new method of payment — “apple pay,” for example.
- Add a cloud database as a secondary database.
A refactored architecture following SRP would look like this:
Key things to remember
- Some benefits of SRP include easier testing, reusable code, better implementation decisions, and adaptive coding.
- A class implementation may vary owing to performance or architectural reasons as requirements change over time. If your classes do not adhere to the Single Responsibility Principle, you will most likely create “God-like” classes with thousands of lines and multiple tasks.
- To avoid over-engineering, only look for causes for change that are likely to occur. We can not anticipate every future change.
What’s next
I hope you enjoyed the first article of the SOLID Principle series. While I work on the remaining concepts, you might be interested in previous articles I authored:
- 3 Design Patterns Every Developer Should Learn
- JavaScript Under The Hood: Advanced Concepts Developers Should Know
- Stack & Tools You Should Learn to Become a Full-Stack Developer Faster
Also, follow me on Medium and LinkedIn to stay up to speed on new posts I create. Cheers!
Bit: Feel the power of component-driven dev
Say hey to Bit. It’s the #1 tool for component-driven app development.
With Bit, you can create any part of your app as a “component” that’s composable and reusable. You and your team can share a toolbox of components to build more apps faster and consistently together.
- Create and compose “app building blocks”: UI elements, full features, pages, applications, serverless, or micro-services. With any JS stack.
- Easily share, and reuse components as a team.
- Quickly update components across projects.
- Make hard things simple: Monorepos, design systems & micro-frontends.
Learn more
- How We Build Micro Frontends
- How we Build a Component Design System
- How to reuse React components across your projects
- Painless monorepo dependency management with Bit
Single Responsibility Principle: Practical Guide to writing maintainable code was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Bits and Pieces - Medium and was authored by Noor Ahmed
Noor Ahmed | Sciencx (2022-06-23T10:20:50+00:00) Single Responsibility Principle: Practical Guide to writing maintainable code. Retrieved from https://www.scien.cx/2022/06/23/single-responsibility-principle-practical-guide-to-writing-maintainable-code/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.