What Is This Principle?

The Single Responsibility Principle (SRP) states that any class must have one, and only one, reason to change. If a class has more than one reason to change, it should be refactored.

Single Responsibility Principle - Just because you can, doesn't mean you should

Benefits

The primary benefit the Single-Responsibility Principle gives you is high-cohesion, low-coupling code.

Cohesion refers to the degree with which elements of code belong together. This means that the level of cohesion between elements is higher if they are related, and lower if they are not. Having a class with many methods that do one job (or aspects of one job, depending on your definition) means that that class has high cohesion.

Coupling is the manner of independence between modules of a programming system. This means that high coupling means that modules are more dependent upon one another, and low coupling means they are less dependent.

Essentially, following SRP minimizes the possible times more than one class will have to change for a given requirement, and maximizes the possibility that changing one class will not impact any other classes. This results in high-cohesion and low-coupling code, an ideal situation when changes arise (as they inevitably do) because it minimizes the impact any changes have on the affected code and its related modules.

A Simple Example

Consider the following class:

public class InvitationService
{
	public void SendInvite(string email, string firstName, string lastName)
    {
    	if(String.IsNullOrWhiteSpace(firstName) || String.IsNullOrWhiteSpace(lastName))
        {
         	throw new Exception("Name is not valid!");
        }
    	
    	if(!email.Contains("@") || !email.Contains("."))
        {
        	throw new Exception("Email is not valid!!");
        }
        SmtpClient client = new SmtpClient();
        client.Send(new MailMessage("[email protected]", email) { Subject = "Please join me at my party!" });
    }
}

This method SendInvite() does, in fact, send an invitation email, but it also validates both that the email address is good and that the user first and last names are not blank. If we start using a different manner to send invitations, this class will have to change. But what happens if, say, we get a more complete validation ruleset for validating email address? This class also needs to change in that case.

Simply put, because it has to change for more than one reason, InvitationService is doing too much. In this particular case, InvitationService has high-coupling (too many interdependent parts) and low-cohesion (unrelated parts are put together). So how can we refactor this to be low-coupling and high-cohesion?

First, let's break out the name validation into its own class.

public class UserNameService
{
    public void Validate(string firstName, string lastName)
    {
        if(String.IsNullOrWhiteSpace(firstName) || String.IsNullOrWhiteSpace(lastName))
        {
            throw new Exception("The name is invalid!");
        }
    }
}

We should also break the email validation into its own class:

public class EmailService
{
    public void Validate(string email)
    {
        if (!email.Contains("@") || !email.Contains("."))
        {
            throw new Exception("Email is not valid!!");
        }
    }
}

Now, each of these classes has only one reason to change. We can use these services in the InvitationService:

public class InvitationService
{
    UserNameService _userNameService;
    EmailService _emailService;

    public InvitationService(UserNameService userNameService, EmailService emailService)
    {
        _userNameService = userNameService;
        _emailService = emailService;
    }
    public void SendInvite(string email, string firstName, string lastName)
    {
        _userNameService.Validate(firstName, lastName);
        _emailService.Validate(email);
        SmtpClient client = new SmtpClient();
        client.Send(new MailMessage("[email protected]", email) { Subject = "Please join me at my party!" });
    }
}

Now each class will have one, and only one, reason to change.

Potential Hazards

The examples I gave above show a simple example of implementing the single-responsibility principle in your code. I believe they give a good demo of what the SRP is all about. However, this principle is so broad, and its justifications so varied, that I'd be remiss if I didn't mention some of the hazards to slavishly adhering to it.

First, there's no hard-and-fast rule about what exactly represents a "reason to change". You could define the example above to say that changes in the validation was not a "reason to change" and thereby claim that the first implementation of InvitationService was valid. We'd have a hearty argument about this, but you could do it.

Second, strictly following this rule could lead you to a lot of one-line methods, something that (it could be argued) causes unnecessary code bloat. While this would certainly be a low-coupling, high-cohesion situation, it may not be the best solution.

Third, this rule is rather difficult to implement after the code has already been written. Trying to implement SRP on a code base that didn't try to implement it at all is often a massive roadblock to refactoring that application. You can do it (and I speak from experience on this one) but it takes a long time and could be considered a total rewrite, depending on the scope and functionality involved.

Is It Worth It?

Absolutely, unequivocally yes. The hazards are minor, and if you keep the SRP in mind from the start, it will be much easier to make single-responsibilities per class a priority in your design. The code that the Single-Responsibility Principle can produce will be much simpler and free of dependencies. As we will see in future installments of this series, having less dependencies is one of the SOLID principles' fundamental ideals.