Building S.O.L.I.D - Single Responsibility Principle

The first principle in S.O.L.I.D is the Single Responsibility Principle. Often, this principle is explained with reference to a quote from Robert C. Martin:

"A class should have only one reason to change".

Let's break this principle apart with an explanation and a concrete example.

The Single Responsibility Principle states that each module should have encapsulate a single responsibility. The idea here is to reduce high coupling in components by reducing the workload each module is concerned with. For example, we may imagine a feature within an application that compiles a report and emails it to an administrator. If a single class handles both these responsibilities, it is in violation of the Single Responsibility Principle. This becomes an issue if in future the report changes - we are at greater risk of breaking the application than if the feature compiling the report was kept separate (that is, it is ONLY responsible for compiling the report).

/*
    Create a new report. Send the email using the compiled report. 
    This is concerned with parsing the report out into HTML, setting 
    up the mailer, and sending the email.  
*/
public class Reports() {
        public IReport CreateReport(string[][] reportData)  {
            // create the report 
        } 

        ...

        // set up the email client
        MailMessage mail = new MailMessage();
        SmtpClient client = new SmtpClient();   

        ...

        StringBuilder body = New StringBuilder();
        foreach (String reportNode in report) {
            // compile into HTML
        } 
        mail.Body = body;

        ...

        // send the email
        client.send(mail);
    }
}

In reference to the example above, we can disseminate multiple reasons we may need to make modifications to the functionality of our report system. For example:

  • We may need to modify the content of the report as business needs change
  • We may need to change how the email is constructed, such as adding additional recipients
  • We may need to change what the email looks like
  • We may need to change email service providers

When our class is highly coupled (has multiple responsibilities), our ability to re-use code is reduced, leading to a code base that is not DRY. Changes to a particular piece of functionality may need to be made in multiple areas. Our ability to make modifications to specific features is also complicated by the fact that changes in one class may break related functionality in the same class - a risk reduced if separate pieces of functionality are encapsulated and kept separate.

/*
    Handles creating a report
*/
public class Reports() {
    public IReport CreateReport(string[][] reportData) {
        ...
    }
}

/*
    Handles converting reports to different types
*/
public class ReportConverter() {
    public string ConvertToHtmlString(IReport report) {
        ...
    }
}

/*
    Handles basic emailing 
*/
public class Emailer() {
    public void SendReport(string title, string body, string[] emailAddresses) {
        ...
    }
}

By breaking the function up into individual classes and functions, we have isolated the different items of functionality of our application. It is now easier for us to make changes to any one part of our reporting process without having to deal with dependency issues within the same module but are concerned with other functions. If the style of the email needs to change, we can apply that change within the ReportConverter only - neither the Report of the Emailer is concerned with the style of the email, or needs to be modified to facilitate the change. Likewise, if we need to switch out the email service provider, only the Emailer will need to change. When testing the application, we are also able to write unit tests that only send emails or that only generates reports; we aren't tied to testing the entire function in one go as it is now far more modular.

Typically, the Single Responsibility Principle results in many more classes and interfaces within a code base.

To summarize, following the Single Responsibility Principle provides us with code that has/is:

  • High code re-use potential (DRY-er code for generic functionality, such as the Emailer in the example above)
  • Increased test-ability (each module can be mocked and tested individually)
  • Lower levels of coupling (dependent code isn't tied together in the same class/functions)
  • Easier to read and understand(as code inter-dependencies are formalized and classes/functions are typically smaller)

This article is my oldest. It is 708 words long, and it’s got 0 comments for now.