September 10, 2022

The Single Responsibility Principle

  —What constitutes a single responsibility?

The Single Responsibility Principle (SRP) is part of the SOLID principles. I like to use SOLID to construct implementations and find it a useful tool to use in discussion about code. It comes up in interviews quite often–people list it on their resumes, so I ask them about it.

Most of the answers I get during those conversations are one-dimensional: SRP is about assigning one responsibility to a class. Yes, but which responsibility? How do you know if it’s the right responsibility?

I think of responsibility as a vector of change. Where the vector of change comprises both known and anticipated changes. In my view, multiple responsibilities are OK if those responsibilities are expected to change together and if their separation leads to a poorer implementation.

So what constitues a single responsibility? It’s the result of balancing the tension between reason to change, coupling and cohesion and separation of concerns.

Reason to change is driven by Parnas’ notion that a key criteria for decomposing systems into modules revolves around:

  • Difficult design decisions.
  • Design decisions which are likely to change.

Design each module to hide exactly one of these decisions. A module is a programming language concept that groups things together. (For example, a class, namespace, module, etc.)

Coupling and cohesion are usually discussed together but it’s important to consider them individually. Meyer’s notion is that low coupling and high cohesion tend to move in tandem and is the desirable state.

Coupling is a structural construct affecting modularity. It is structural in the sense that you don’t want things to depend upon or know about other parts of the design and implementation if they don’t have too.

Low coupling is characterized by

  • few interfaces that limit the number of interconnections between modules. The number connections to a module includes interfaces like procedure calls and data sharing, etc.

  • small interfaces that share as little information as possible and don’t leak abstractions.

Where coupling occurs explicit interfaces and explicit coupling should be used. Explicit interfaces and coupling rely upon the programming language to make them clear.

Cohesion is a conceptual construct. It is conceptual in the sense that you want like things to belong together so that you can reason about one thing at a time. High cohesion results a peaceful and honest coexistence between concepts.

The best types of cohesion arise from (good to best):

  • Communicational and informational cohesion occurs whenever elements that use the same data are grouped together.
  • Sequential cohesion occurs whenever elements that have a producer/consumer relationship are grouped together.
  • Functional cohesion occurs whenever elements that contribute to a single well-defined task are grouped together.

Separation of concerns is a conceptual tool that permits identifying differences within Jackson’s notion of problem, domain and application. In order to achieve separation of concerns for a problem you must identify the principle parts of the problem.

Identifying the principle parts of the problems, permits the identification of subproblems that need to be solved. Subproblems have different domains.

A domain is part of a whole, but exhibits two important properties:

  1. It has its own set of internal properties.
  2. It has its own set of behaviours.

Taken together these properties and behaviours are largely independent of everything else. For the structuring to work well the collection of phenomena that are private to each domain must be richer and more interesting than what is shared between domains.

You can view separation of concerns as defining layers of abstractions with each abstraction having distinct semantics. The semantics need to be consistent and different parts of the same abstraction should share the same semantics. That is the semantic gap between things should be small.

A single responsibility should yield a simple and easy to understand description of a domain. That domain’s description must be rich enough to be more interesting than what is shared between the domains. Multiple domains interact to solve problems and solutions to problems leads to useful applications.

Importantly, I’m not assigning single responsibility to a class. I’m assigning it to a domain.

A domain that exhibits a rich set of behavours that fit well together and whose proximity is not jarring. That domain may be represented using a class, but the driver for that class’ existence is it’s domain and the problem it solves in an application.

comments powered by Disqus