Single Responsibility Principle (SRP) is the first of the five SOLID principles, which were researched by Robert Cecil Martin. What became knowns as SOLID was firstly described by “Uncle Bob” in March of 1995 [1] on comp.object newsgroup as a part of “The Ten Commandments of OO Programming” proposal [3].

What is single responsibility principle? In short the principle says that a class should have only one reason to change. So why is it called a “single responsibility principle” and not, say “single reason to change principle”? Martin in his book “Agile software development” [2] refers to Tom DeMarco and Meilir Page-Jones, who researched the principle before and called it cohesion. Their definition focused on functional relationships between elements of a class or a module, hence “responsibility”. Martin refines the principle formulation to make it more objective and applicable, but the meaning is still the same - if a class has more than one reason to change, it presumably means that it has more than one responsibility and vice versa. The single responsibility principle is also present in the Unix philosophy, where it is stated that programs shall do one thing and do it well [4].

More detailed look

Let’s take a look at Single Responsibility Principle by an example. I want to open some discussion later, so I will use the same example as in the Martin’s book [2]. The example is shown on the diagram below.

classDiagram

Rectangle <-- `Graphical Application`
GUI <-- `Graphical Application`
Rectangle --> GUI
`Computational Geometry Application` --> Rectangle
  class Rectangle {
    +draw()
    +area() double
  }

  class `Graphical Application`:::app {
  }

  class `Computational Geometry Application`:::app {
  }

  class `GUI`:::app {
  }

We have a class called Rectangle, which provides two public methods: draw() and area(). There are two applications, which use Rectangle for their own purposes - Graphical Application, which makes use of draw() function to render the rectangle and Computational Geometry Application, which uses area() function to perform some calculations. Because Rectangle deals with rendering it has a dependency on GUI component, which is provided by some GUI library.

One consequence of this design is that Computational Geometry Application depends on GUI and the library providing it would need to be linked in into Computational Geometry Application, even though Computational Geometry Application does not need GUI by itself. Secondly, any change to Rectangle will require recompilation of both: Computational Geometry Application and Graphical Application.

Robert Martin proposes to break Rectangle into two separate classes as shown on the diagram below.

classDiagram
Rectangle <-- `Graphical Application`
GUI <-- `Graphical Application`
Rectangle --> GUI
Rectangle --> GeometricRectangle
`Computational Geometry Application` --> GeometricRectangle
  class Rectangle {
    +draw()
  }
  class GeometricRectangle {
    +area() double
  }
  class `Graphical Application`:::app {
  }
  class `Computational Geometry Application`:::app {
  }
  class `GUI`:::app {
  }

Indeed, we got rid of unwanted GUI dependency of Computational Geometry Application and modification of draw() function can not affect GeometricRectangle (changes to GeometricRectangle will affect Rectangle and Graphical Application however).

In the above example we initially had two reasons to change the Rectangle class: we might need to change the way it renders itself, or we might want to change the way it calculates the area. More importantly the change could be caused by change of requirements of GUI classes (Graphical Application in the book, which I believe is a clerical error, since it is Graphical Application, which depends on Rectangle not the other way around). As a remedy we turned Rectangle into two classes that both have single reason to change, hence both classes have single responsibility and we conform to SRP. Well… In real life things aren’t usually so simple.

Discussion

The problem with the examples is that in comparison to real life scenarios, they can be over-idealized. Let’s say we would need to make certain operations on triangulated model of the rectangle. Our GeometricRectangle API could be extended like this:

classDiagram

class GeometricRectangle {
    +area() double
    +triangulated() Triangulation
}

Let’s assume that we have added additional Triangulated Geometry Application, which would make use of triangulated() method. To perform the triangulation we might want to use a popular CGAL library.

classDiagram

GeometricRectangle <--  `Computational Geometry Application`
Triangulation <-- `Triangulated Geometry Application`
GeometricRectangle --> Triangulation
`Triangulated Geometry Application` --> GeometricRectangle

  class GeometricRectangle {
    +area() double
    +triangulated() Triangulation
  }

  namespace CGAL {
    class Triangulation
  }

  class `Triangulated Geometry Application`:::app {
  }
  class `Computational Geometry Application`:::app {
  }

The history repeats - we have two methods, dependency on CGAL library which needs to be linked into two different applications and two reasons to change GeometricRectangle class. Even though dependencies are just like in initial example things are much more blurry. Shall we break GeometricRectangle class again? This would most likely require a refactoring of the existing code. But what if Computational Geometry Application would want to use triangulated() method at some point? Moreover, the GeometricRectangle now depends itself on external CGAL library, so GUI classes would depend on it as well and we can’t get rid of one or another by simply extracting relevant portion of API into a separate class.

To add even more pepper, consider that in real-life frameworks you will see graphical aspects (e.g. rendering and appearance) mixed altogether with computational geometry aspects (e.g. transformations). Just take a look at Qt Quick’s Rectangle component or Flutter’s Container class.

Does it mean that designers of state-of-the-art frameworks never heard about SRP? Or perhaps, SRP is wrong? No and no. The problem lies in the scale and context.

Martin in his example uses two extremely specialized applications (one for computing the area and the second one solely for drawing). This shows the point, but such finely granular architecture is never found in real life. Neither Rectangle nor GeometricRectangle live without a context. It is the surrounding which defines their “reason to change”. In real life you can’t expect that your class will be ever used by only two other components. In fact, a good designer should assume that his class will be used by indefinite amount of users. The “reason to change” becomes a statistics. Qt Quick and Flutter provide a set of methods useful primarily for all sorts of GUI-related operations for GUI applications. Even though some of the methods could be used by computational geometry applications this can’t affect the design. As a software designer you can put a fence - this class is not intended to be used by computational geometry applications. You can’t really predict what someone would want to do with your class, but you can tell them how you intended the class to be used and serve the majority of users. And you should see “reason to change” in terms of use cases, rather than individual associations.

To sum it up, Single Responsibility Principle is an important concept, which should guide your designs and architecture. You need to look at it from a proper perspective. Other software principles, good practices and your own experience should help you find this perspective.

References

  1. R. C. Martin, The Principles of OOD, www.butunclebob.com. http://www.butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod (accessed: Mar. 31, 2024).
  2. R. C. Martin, Agile software development, principles, patterns, and practices: Pearson new international edition. London, England: Pearson Education, 2013.
  3. R. C. Martin, The Ten Commandments of OO Programming. object.comp newsgroup, Mar. 1995.
  4. E. S. Raymond, “Chapter 1. Philosophy”, in The Art of Unix Programming. Eric S. Raymond, 2003. Accessed: Mar. 31, 2024.