Single Resposibility Principle
The Single Responsibility Principle (SRP) is one of the SOLID principles in object-oriented programming. It states that a class should have only one reason to change, meaning it should have only one responsibility. Here's an in-depth explanation of SRP with examples and best practices.
Overview:
The Single Responsibility Principle is the "S" in SOLID, a set of principles designed to make software more maintainable and scalable. SRP encourages a clear and focused design, ensuring that each class or module has a well-defined responsibility.
Key Points
One Reason to Change: A class should have only one reason to change. If a class has more than one responsibility, changes to one responsibility may affect the others, leading to code that is harder to maintain and understand.
Separation of Concerns: Divide your system into smaller components, each responsible for a specific concern. This promotes modular and maintainable code.
Example of Violating SRP
public class EmailService
{
public void SendEmail(string to, string subject, string body)
{
// Code to send an email...
}
public void SaveEmailLog(string to, string subject, string body)
{
// Code to save email log to a database...
}
}
In this example, EmailService violates SRP by handling both email sending and email logging. A change in email sending logic may affect the email logging functionality.
Applying SRP
public class EmailService
{
public void SendEmail(string to, string subject, string body)
{
// Code to send an email...
}
}
public class EmailLogger
{
public void SaveEmailLog(string to, string subject, string body)
{
// Code to save email log to a database...
}
}
By separating responsibilities into EmailService and EmailLogger, each class adheres to SRP.
Benefits
Improved readability and maintainability. Easier testing and debugging. Minimized ripple effects of changes. Best Practices: Focus on a Single Task:
Design classes to perform a single, well-defined task. This makes it easier to understand, test, and modify the code.
Refactor When Necessary:
Regularly review and refactor your code to ensure that classes maintain a single responsibility. Identify and address violations of SRP.
Use Composition:
Instead of trying to fit multiple responsibilities into one class, use composition to combine smaller classes that each handle a single responsibility.
Adapt to Change: Design your classes to be open for extension but closed for modification. This allows you to add new functionalities without modifying existing code.
Clear Naming:
Choose clear and descriptive names for your classes and methods to indicate their purpose. If a name becomes ambiguous, it might be a sign of a violated SRP.
Conclusion
Applying the Single Responsibility Principle results in code that is more modular, easier to understand, and less prone to bugs. It contributes to the overall maintainability and flexibility of the software system. By adhering to SRP, you create classes that are focused, cohesive, and resilient to changes.
I hope this can help you to understand the Single Responsibility Principle. I'd love to see what you can come up with!
Thank you for reading, and feel free to comment or connect with me LinkedIn and GitHub .
Click here for Open/Closed Principle
Top comments (14)
Thanks for the write-up. I love reading these kinds of subjects.
If I may ask, why do you believe that an arbitrary EmailService should not include email logging? Imo, an EmailService could encompass both sending of email and handling email logging (for example writing to logs when sending an email fails).
I am wondering if there is an over-arching technical reason for not including it if the developer wants to. Now, if you wanted to just create a class called MailSendService, then perhaps that makes sense to me. Anyways, just trying to learn and would love to hear your opinion. Thank you!
I was wondering the same as you.
For me, EmailService could easily handle EmailSending and EmailLogging. I didn't grasp the reason not to do it and why the separation was considered SRP based on Clean Code.
Thanks for the sanity check :-)
EmailServices and EmailLogging are two individual tasks, so we cannot create one class according to the Single Responsibility Principle (SRP).
In my opinion, the example is valid because it's following the single responsibility principle. Other word is wether the logger must be used inside the service class or not. So, as an example to understand the concept I don't see anything wrong.
Thanks Jaime! Appreciate your insight.
Well it makes sense and not. It depends how you abstract this problem on your own project and possibly way of your thinking.
If you have simple project then this is possibly overkill. If you work in banking system, than yes. It make sense as you can switch internals of this service without being afraid your email notifications won't get to the customers - which is a lot of customers
All depends on use-case and how you manage other principles - e.g. KISS.
EDIT:
From the post it may seem that simple projects don't need to do this or more robust projects needs to do this. That is not the idea behind it. Just explanation of mine. It all depends on other factors like time, budget etc
There are two aspect you did not mention.
SRP helps you, to improve maintainibility. Often it is easier to copy and paste the same code whereever it is needed. But if you need to change things in an application, you need to find and change all instances. That is a common source of trouble with larger applications, it is easy to forget some instances. It is much more save to have only a single source here.
The whole principle of inheritance was mainly invented to allow SRP over several classes. If you take - for example - the DOM. This provides multiple classes to display things on a web site. But they all are derived from the same base classes. Things that multiple classes have in common are part of the parent class, so they have to be written only once. If you need to change thins in such a system, there is only one source of truth - here one code segment - that is responsible. And this code segement should only be written once.
Most OOP applications are built this way. Sometimes, you build an "abstract" base class that is never instanitated, only to have a place where a common routine can be placed.
Surely, this system would be limited. Assume you build a graphics application that has a base class that can draw a dot. This dot has two coordinates, x and y. You write all the stuff you need for drawing, mouse interactions, storing the position and so on for this dot.
Now you derive a class from this base class, that can draw a line. You will need a second coordinate to draw the line, x1 and y1. If you derive the line from the dot, you will inherit anything that was containted in the base class, like drawing, mouse interaction, storing and so on.
But how should the base class handle the new coordinates? They are not known to the routines? That is the nice thing about OOP, you can extend the base routines where necessary, often by first calling the "super" routine (means, the same routine in the parent class), and then adding all the things you need to handle your extension. This can often be done in a very surgical manner adding only the absolute minimum.
The nice thing here is, that all the things you implemented in your base class stay untouched,** they are still the source of truth** for the main interactions. Done the right way, you can apply large changes to the core classes without chaning the derived classes, so if you implement a new capability, the whole family will inherit this capability. Maybe you started your application drawing a dot only in black. Now, you add a color to the base class which is applied to the cancas before drawing the dot. Then all your derived class will also be able to draw lines, circles, rectangle in color, even if you did not change a single line in this derived classes.
SOLID and the other OOP principles (KISS, YAGNI, DRY/WET, GRASP, Package Principles, et cetera) are all band-aids for shortcomings in OOP.
(Them's fighting words!)
Before we had OOP, we had procedural programming. OOP became the hot new paradigm to cure all our woes with procedural programming. And prior to OOP, procedural programming itself had changed over time with the advent of structured programming.
Well, OOP did cure many of our woes with procedural programming. But after years and years, OOP created some of its own woes. SOLID (and friends) are to address the woes that OOP brings to the table.
In time, I expect a new programming paradigm to become the next hot new paradigm, and unseat OOP. (OOP will never "go away", it'll always be around. Just like procedural languages are still around. It'll just become less prominent.)
Will the next craze be FP (like C# with LINQ)? FFFP (like F#)? DSL (like JetBrains MPS)? PPL (like Lisp)? Algebraic (like Mathematica)? Retro revival (like beautiful and concise APL)? Simonyi's Intentional Programming? Knuth's Literate Programming? DOD (like Blow's Jai)? Something else? Exciting times!
Sorry, but paradigms are not religion. You don't have to be either Catholic or Protestant.
There had been different ideas on what OOP should be in the past, but as implemented in C++ it was just a way to isolate parts of your code, thus reducing complexity. And it was a clever way to reuse code without loosing the single source of truth. It was never a holy grail the cured all your programming issues, specially with an increasing number of people that did not even know how to use these tools.
And how should they? Javascript initially had no class concept, and what we have now is only a very lightweight shadow of what classes should be. No encapsulation, no fine grained scoping.
You should explain a bit more, which "whoes" OOP could not cure? Maybe it is more a problem of your expectation. If you put enough spaghetti into a class, the result will still be spagehtti. This is the same with all paradigms.
Functional progamming makes your programs more reliable. But internally, a class works like a small programm, so there is absolutely no reason why you should not apply the principles of FP to class methods.
OOP was invented to solve the problems of large procedural codebases. But did people switch and write only OOP-code? No, they did not, They continued to write procedural code, nut now inside OOP-classes and only where it was appropriate. Just because we establish a new paradigms, this does not mean, we forget about anything we have learned before. We're just increasing our toolbox, nothing else.
Very good article with a clear example. There is a very good article on Digital Ocean that helps solidify this digitalocean.com/community/concept...
Good article but I think you confused SRP with SoC. The final version of SRP says "A module should be responsible to one, and only one, actor." (From "Clean Architecture" by Robert C. Martin, Chapter 7 "SRP: The single responsibility principle").
For example: The EmailService is responsible to the marketing unit of the company. Any other unit of the company may not cause changes in the EmailService.
In that case your code does not violate SRP but SoC.
(I did this mistake also in the past π )
Nice article !!
and great discussion :)
Thank you all for sharing your thoughts. Your contributions are greatly appreciated.