S.O.L.I.D. principles seem to be a hot topic. The guys over at LosTechies.com explored it last year in March as their topic of the month and did a great job of it. I know when I read blogs I enjoy when different authors post on similar topics. I find that often the different examples and explanations paint a clearer picture than can be had with only one example. I'd like to share an area where we use the Single Responsibility Principle (SRP) in our application
Note: The "aha" moment won't be some thrilling display of how we averted some disastrous bug or how we eliminated some thousands of lines of code. It will be much more subtle but that's ok. A good design should strive for an element of simplicity. Great design is asking to hear the answer to a riddle and then thinking, "duh! of course" When you hear the answer you realize how "obvious" it was.
In our application we queue all emails to be handled by a different process since sending email is a blocking call which slows your response times (Note to BCL junkies: yes I know there is a SendAsync method). There are a few instances in our application where we want email to be sent right away and not queued. An example of this would be a confirmation email for a new account or a password reset request.
We already had a QueuedEmailService class so adhering to SRP, I built a RealTimeEmailService class whose job, and only job, it is to send the email right away. Thinking of responsibility driven design, classes:
- Know things
- Do things
- Make decisions
With that in mind I needed another class to make the decision to when to send the email through the RealTimeEmailService or when to use the QueuedEmailService:
(At this point some of you might be reading thinking how easy of a problem this would be to solve and you're quite right. What I want to point out is not the problem, but how one solves the problem. Nearly anyone could've gone into the QueuedEmailService and added an if statement and sent emails real-time if that is what is needed, thus ignoring SRP. By lumping all of the work together you aren't building object oriented software...sorry. And while you might be okay with that and you're software WILL work, you run the risk of more maintenance issues and modularity problems).
Here's what a quick summary of what I've got:
1: public QueuedEmailService : IEmailService
3: public void Send(...)
5: // add to some queue
9: public RealTimeEmailService : IEmailService
11: public void Send(...)
13: // send right now
17: public PriorityBasedEmailService : IEmailService
19: public void Send(...)
21: if (priority == MailPriority.High)
22: // send using realtime service
24: // send using queued service
I know many people who would look at the code above and think it is possibly overkill. I would disagree for a few reasons:
- Each class does only one thing. If we have a problem with some piece of functionality it is much easier to debug. Each of the three classes above in their full implementation will fit on your screen without the need for scrolling. It's quite refreshing.
- Modular design - since the application only knows that it's dealing with an IEmailService I can easily swap out implementations. For example, if we decide that we want all emails to be sent real-time, I just change how I've configured Windsor (my IoC container) and it works, because I have a concrete class which sends real-time. If you were to combine all of your code into one class, you'd have to go back and change your code and go through the pain of deployment.
- Open-Closed principle - since each of the classes only have only one thing they do, I can more easily say they are closed for modification, reducing the chance that I introduce a bug later on. If you put this code into one big method, you can change the code to meet your new needs, but each change introduces the possibility of introducing a bug.
There are cases when a design can go overboard, but for the vast majority of cases this simply isn't the case. I'd rather see a solution where someone has gone too far than what I typically see in classes that do it all, with no clear responsibility.
I hope this helps augment your knowledge of SRP. If you're still a little fuzzy on SRP and what it is, let me know, I'd love to discuss it with you.