12 August 2021

SOLID: The Single Responsibility Principle (SRP)

Software entities should have only one reason to change.

Introduction

Robert C. Martin coined the mnomonic acronym SOLID to represent 5 principles of software design. This is the first in my own five-part series exploring my understanding of and experience applying these principles:

  1. [SRP] The Single-Responsibility Principle: (you are here)
    • "Software entities should have only one reason to change."
  2. [OCP] The Open–Closed Principle:
    • "Software entities should be 'open for extension but closed for modification'."
  3. [LSP] The Liskov Substitution Principle:
    • "Subtypes must be substitutable for their base types."
  4. [ISP] The Interface Segregation Principle:
    • "Clients should not be forced to depend on methods they do not use."
  5. [DIP] The Dependency Inversion Principle:
    • "High-level policy should depend upon abstractions, not concretions."

SRP

It's tempting to think of SRP in terms of doing "one thing", but in practice this ends up being vague and unhelpful. A function doing 'one thing' is very different from a class or module or repository doing just 'one thing'. So, the real foundation of SRP is about separating things that change for different reasons and at different times.

Software entities should have only one reason to change.

This principle applies at all levels of software development:

Zoomed Out: The Repository

Since software repositories are often the unit of versioning it wouldn't make sense to combine components that change on different schedules. Stable, general-purpose, low-level libraries shouldn't be paired with high-level, business-specific use-cases and rules. They change for different reasons and at different times.

Zoomed In: Classes and Functions

Consider a Bowling class that provided methods for calculating the score of a tenpin bowling game as well as rendering itself on a screen:


                              +-------------------+
                              |                   |
                              |     Bowling       |
                              |                   |
                              |   +render()       |
                              |   +score():int    |
                              |                   |
                              +-------------------+

This class will be depended upon by GUI code as well as business use-case code and therefore has more than one reason to change (violating SRP):


+-------------------+         +-------------------+
|                   |         |                   |           +---------------+
|    Statistical    |         |  Bowling Game     |           |               |
|     Modeling      |         |                   |           |   Graphical   |
|    Application    +-------->|   +render()       |<----------+  Application  |
|                   |         |   +score():int    |           |               |
|                   |         |                   |           |               |
+-------------------+         +----------+--------+           +-------+-------+
                                         |                            |
                                         |                            |
                                         v                            |
                                  +------------+                      |
                                  |            |                      |
                                  |    GUI     |<---------------------+
                                  |            |
                                  +------------+

It seems strange at first, but a good solution is to have two bowling classes, one to meet the needs of the GUI and another to meet the needs of the business use-cases:


+-------------------+      +-------------------+
|                   |      |                   |
|    Statistical    |      |                   |
|     Modeling      |      |     Graphical     +---------------+
|    Application    |      |    Application    |               |
|                   |      |                   |               |
|                   |      |                   |               |
+---------+---------+      +---------+---------+               |
          |                          |                         |
          |                          |                         |
          v                          v                         v
+-------------------+      +-------------------+      +-----------------+
|                   |      |                   |      |                 |
| Scoring Rules     |      |  Bowling Game     |      |                 |
|                   |      |                   |      |       GUI       |
|   +score():int    | <----+   +render()       +----> |                 |
|                   |      |                   |      |                 |
|                   |      |                   |      |                 |
+-------------------+      +-------------------+      +-----------------+


Next Section: The Open/Closed Principle