Single Responsibility Principle (SRP): A SOLID Pattern for Clean Code

single responsibility principle 1 overcoded
The SRP states that a class or object should bear responsibility for a single part of a program's functionality.

The Single Responsibility Principle (SRP) is one of five design principles of the SOLID design framework for object-oriented software design. The SRP dictates that classes should have only a single reason to change. Multiple reasons for change indicate more tightly-coupled designs that are more rigid and harder to maintain.

A simple example of the SRP is illustrated here by a stock trading application that has a Transaction class. This class is responsible for two actions: buying and selling of stocks. The SRP is violated by defining these functions within the class itself, resulting in multiple responsibilities. By refactoring these actions into separate classes one limits the number of reasons that necessitate a change to the Transaction class (see below.)

Highlights

  • SRP is loosely based on the concept of cohesion
  • SRP was first defined by Robert C. Martin in 2002 as part of his interpretation of AGILE software development practices
  • SRP is part of a five-factor set of Object-Oriented Design principles known as SOLID
  • SRP advises that class-based design should focus on single functionalities
  • Multiple responsibilities are a “code smell” and hint at a poor software design

Introduction

The concept of SRP can be traced back to the concept of cohesion first outlined as early as 1979 by Tom Demarco in Structured Analysis and System Specification (1). SRP is a bit different and focuses more explicitly on the role of responsibility.

SRP is one of five class-focused design principles of the SOLID framework for better object-oriented software design. This framework, including the SRP, was concretely introduced by renowned computer scientist Robert C. Martin in his 2002 book Agile Software Development, Principles, Patterns, and Practices (2).

The Single Responsibility Principle (SRP) dictates classes should have one responsibility and one responsibility only.  Martin describes responsibility as an “axis of change” such that they represent possible rigidity in design (a design smell.)

Examples

As an example of the single responsibility principle, consider a class for a stock trading application that buys and sells securities. This class, named Transaction, has two responsibilities: buying and selling. In our first example, this is achieved by defining two methods of which the Transaction class will be responsible for defining behavior.

Multiple Responsibilities

// Class to handle both Buy and Sell actions
class Transaction {

    // Method to Buy, implemented in Transaction class
    private void Buy(String stock, int quantity, float price){
        // Buy stock functinality implemented here
    }

    // Method to Sell, implemented in Transaction class
    private void Sell(String stock, int quantity, float price){
        // Sell stock functionality implemented here
    }
}

This design stands in violation of the SRP due to the fact that if the requirements for either method change it would necessitate a change in the Transaction class. In other words, Transaction has multiple responsibilities. Refactoring these methods into separate classes would be one approach to applying the Single Responsibility Principle to this application.

Single Responsibilities

class Transaction{

    // Method to Buy, implemented in Buy class
    private void Buy(String stock, int quantity, float price){
        Buy.execute(stock, quantity, price);
    }

    // Method to Sell, implemented in Sell class
    private void Sell(String stock, int quantity, float price){
        Sell.execute(stock, quantity, price);
    }
}


class Buy{

    // Static method, accessible to other classes, to execute Buy action
    static void execute(String ticker, int quantity, float price){
        // Execute buy action here
    }
}

class Sell{

    // Static method, accessible to other classes, to execute Sell action
    static void execute(String ticker, int quantity, float price){
        // Execute sell action here
    }
}

This change reflects a refactoring of the Buy and Sell methods into two separate classes. Each of those refactored classes is then tasked with a single responsibility. The Transaction class can still perform two separate tasks but is no longer responsible for their implementation.

Martin defines “responsibility” as a reason to change. In our hypothetical trading application, the first Transaction class would have a reason to change if we had to adjust how stocks were bought or sold (two reasons.)

Maybe the application uses an API of a third party that changes syntax, maybe Buy transactions are made through a different broker than Sell transactions, or maybe we just decide to label our transaction records differently. In any case, the related method in the Transaction class would require a change.

With the Buy and Sell actions as separate classes, the only changes to the Transaction class would be necessitated by other factors. Maybe we add another action like MakeRecord. Maybe we add a DateTime requirement. The possible extensions are not restricted but their implementation should retain the goal of avoiding multiple reasons to change the Transaction class.

Final Thoughts

Martin notes at the ending chapter on SRP that this is one of the simplest principles of SOLID yet one of the most elusive to get right. Continually refactoring, checking for multiple responsibilities, and keeping an eye (nose) out for code smell can help ensure SRP is well-realized.

As with any design principle, there is no universal necessity of application. I can even imagine cases where adhering to the SRP principle may violate higher-order goals like avoiding unnecessary complexity. After all, if refactoring classes for a simple application results in obfuscated code, superfluous inheritance, and unclear functionality—one may be better served reverting to the practice of Keeping It Simple Stupid (KISS).

References

  1. Meyer, Bertrand. Object-Oriented Software Construction. Prentice-Hall, 1994
  2. Martin, Robert C. Agile Software Development, Principles, Patterns, and Practices. 1st ed., Pearson, 2002.
Zαck West
Full-Stack Software Engineer with 10+ years of experience. Expertise in developing distributed systems, implementing object-oriented models with a focus on semantic clarity, driving development with TDD, enhancing interfaces through thoughtful visual design, and developing deep learning agents.