Single Responsibility Principle (SRP)

Single Responsibility Principle or simply SRP is the simplest among 5 principles, SOLID, of object oriented programming. My intent behind this post is to make it even simpler to understand for those who are just starting.

The principle as defined by Robert Martin in his book "Agile Principles, Patterns and Practices" says:
A class should have only one reason to change.

This principle relates "reason to change" with "responsibility" the class is intended to perform. In other words, a class should should perform a single responsibility and the reason for the class to change would be the change in it's responsibility.

Let's look at a real world example below:

   1:  public class UserService
   2:  {
   3:      public bool UserExists(string email)
   4:      {
   5:          try
   6:          {
   7:              return true; // already registered (checked in database)
   8:          }
   9:          catch (Exception ex)
  10:          {
  11:              Console.WriteLine(ex.Message);
  12:              return false;
  13:          }
  14:      }
  16:      public bool RegisterUser(string email)
  17:      {
  18:          return true;
  19:      }
  20:  }

What is "Single" responsibility?

Understand that single responsibility does not mean a class can have only one method. I loved the "making breakfast" example given by valenterry in his response to this SO question. He says:
imagine in a common 4 person family there is one familiy member responsible for making the breakfast. Now, to do this, one has to boil eggs and toast bread and of course set up some healthy cup of green tea (yes, green tea is best). This way you can break "making breakfast" down into smaller pieces which are together abstracted to "making breakfast". 

What is the problem here?

As you can see this class has two distinct responsibilities.
  1. User related activities (checking user registration and registering the user)
  2. Logging the exceptions, if any
Currently it prints logs to the console. Few days later, we decide to write logs to a file, database or somewhere else. Clearly, we will need to swap all the Console.WriteLine calls. That means UserService now has a reason to change although there's no change in how it performs user related functionality which is supposed to be its sole responsibility.

How do we solve this problem?

We need to design UserService class such that it is not concerned about anything except performing user related activities.

Let's create a separate class Logger and move the logging code into a Log method.

   1:  public class Logger
   2:  {
   3:      public void Log(string message) {
   4:          Console.WriteLine(message);
   5:      }
   6:  }

Now the UserService class will look like:

   1:  public class UserService
   2:  {
   3:      private Logger logger;
   5:      public UserService() {
   6:          logger = new Logger();
   7:      }
   9:      public bool UserExists(string email) {
  10:          try {
  11:              return true; // already registered (checked in database)
  12:          }
  13:          catch (Exception ex) {
  14:              logger.Log(ex.Message);
  15:              return false;
  16:          }
  17:      }
  19:      public bool RegisterUser(string email) {
  20:          return true;
  21:      }
  22:  }

Now UserService class is no more concerned or does not even know about what Logger class does internally. All it knows is to call a Log method. In future we will have to modify only Logger class if there is any change in logging mechanism, which is it's responsibility.

Note that Logger we created above is still not flexible enough, we will see how it can be improved when we touch on other principles.

No comments:

Post a Comment