Single Responsibility Principle – What Constitutes a Responsibility?

Architectureclass-designsingle-responsibilitysolid

It seems pretty clear that "Single Responsibility Principle" does not mean "only does one thing." That's what methods are for.

public Interface CustomerCRUD
{
    public void Create(Customer customer);
    public Customer Read(int CustomerID);
    public void Update(Customer customer);
    public void Delete(int CustomerID);
}

Bob Martin says that "classes should have only one reason to change." But that's difficult to wrap your mind around if you're a programmer new to SOLID.

I wrote an answer to another question, where I suggested that responsibilities are like job titles, and danced around the subject by using a restaurant metaphor to illustrate my point. But that still doesn't articulate a set of principles that someone could use to define their classes' responsibilities.

So how do you do it? How do you determine which responsibilities each class should have, and how do you define a responsibility in the context of SRP?

Best Answer

One way to wrap your head around this is to imagine potential requirements changes in future projects and ask yourself what you will need to do to make them happen.

For example:

New business requirement: Users located in California get a special discount.

Example of "good" change: I need to modify code in a class that computes discounts.

Example of bad changes: I need to modify code in the User class, and that change will have a cascading effect on other classes that use the User class, including classes that have nothing to do with discounts, e.g. enrollment, enumeration, and management.

Or:

New nonfunctional requirement: We'll start using Oracle instead of SQL Server

Example of good change: Just need to modify a single class in the data access layer that determines how to persist the data in the DTOs.

Bad change: I need to modify all of my business layer classes because they contain SQL Server-specific logic.

The idea is to minimize the footprint of future potential changes, restricting code modifications to one area of code per area of change.

At the very minimum, your classes should separate logical concerns from physical concerns. A great set of examples can be found in the System.IO namespace: there we can find a various kinds of physical streams (e.g. FileStream, MemoryStream, or NetworkStream) and various readers and writers (BinaryWriter, TextWriter) that work on a logical level. By separating them this way, we avoid combinatoric explosion: instead of needing FileStreamTextWriter, FileStreamBinaryWriter, NetworkStreamTextWriter, NetworkStreamBinaryWriter, MemoryStreamTextWriter, and MemoryStreamBinaryWriter, you just hook up the writer and the stream and you can have what you want. Then later on we can add, say, an XmlWriter, without needing to re-implement it for memory, file, and network separately.

Related Topic