214

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?

20
  • 29
    Post to Code Review and be ripped apart :-D Commented Mar 27, 2017 at 19:50
  • 8
    @JörgWMittag Hey now, don't scare people away :)
    – Flambino
    Commented Mar 27, 2017 at 21:56
  • 126
    Questions like this from veteran members demonstrate that the rules and principles we attempt to hold to are by no means straightforward or simple. They're [sort of] self-contradictory and mystical ... as any good set of rules should be. And, I'd like to believe questions like this humble the wise, and give hope to those feeling hopelessly stupid. Thanks, Robert!
    – svidgen
    Commented Mar 27, 2017 at 21:58
  • 9
    For a few weeks now I've been hesitating to ask a reverse question: "What are the clues, that one is splitting single responsibility between too many classes?" I believe that answers too such question could also be useful in answering the OP's question. Commented Mar 28, 2017 at 10:16
  • 9
    @rmunn: or in other words - big rep attracts even more rep, because nobody cancelled basic human prejudice on stackexchange
    – Andrejs
    Commented Mar 31, 2017 at 8:27

16 Answers 16

122

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.

27
  • 38
    While I agree with thinking ahead, there are principles like YAGNI and methodologies like TDD thar suggest the opposite. Commented Mar 27, 2017 at 19:59
  • 90
    YAGNI tells us not to build stuff we don't need today. It does not tell not to build stuff in a way that is extensible. See also open/closed principle, which states "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification."
    – John Wu
    Commented Mar 27, 2017 at 20:26
  • 20
    @JohnW: +1 for your YAGNI comment alone. I can't believe how much I have to explain to people that YAGNI is not an excuse to build a rigid, inflexible system that cannot react to change -- ironically the very opposite of what SRP and the Open/Closed principals are aiming for. Commented Mar 27, 2017 at 20:30
  • 37
    @JohnWu: I disagree, YAGNI tells us exactly not to build stuff we don't need today. Readability and tests, for example, is something a program always needs "today", so YAGNI is never an excuse not to add structure and injection points. However, as soon as "extensibility" adds significant cost for which the benefits are not obvious "today", YAGNI means to avoid this kind of extensibility, since the latter leads to overengineering.
    – Doc Brown
    Commented Mar 27, 2017 at 21:18
  • 9
    @JohnWu We did switch from SQL 2008 to 2012. There were a total of two queries that needed changing. And from SQL Auth to trusted? Why would that even be a code change; changing the connectionString in the config file is enough. Again, YAGNI. YAGNI and SRP are sometimes competing concerns, and you need to judge which one has the better cost/benefit.
    – Andy
    Commented Mar 27, 2017 at 21:23
80

Practically speaking, responsibilities are bounded by those things that are likely to change. Thus, there's no scientific or formulaic way to arrive at what constitutes a responsibility, unfortunately. It's a judgement call.

It's about what, in your experience, is likely to change.

We tend to apply the language of the principle in a hyperbolic, literal, zealous rage. We tend to split classes because they could change, or along lines that simply help us break problems down. (The latter reason isn't inherently bad.) But, the SRP does not exist for its own sake; it's in service to creating maintainable software.

So again, if the divisions aren't driven by likely changes, they're not truly in service to the SRP1 if YAGNI is more applicable. Both serve the same ultimate goal. And both are matters of judgement--hopefully seasoned judgement.

When Uncle Bob writes about this, he suggests that we think of "responsibility" in terms of "who's asking for the change." In other words, we don't want Party A to lose their jobs because Party B asked for a change.

When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function. You want to isolate your modules from the complexities of the organization as a whole, and design your systems such that each module is responsible (responds to) the needs of just that one business function. (Uncle Bob - The Single Responsibility Principle )

Good and experienced developers will have a sense for which changes are likely. And that mental list will vary somewhat by industry and organization.

What constitutes a responsibility in your particular application, at your particular organization, is ultimately a matter of seasoned judgement. It's about what's likely to change. And, in a sense, it's about who owns the module's internal logic.


1. To be clear, that doesn't mean they're bad divisions. They could be great divisions that improve code readability dramatically. It just means they're not driven by the SRP.

13
  • 13
    Best answer, and actually quotes Uncle Bob's thoughts. As for what is likely to change, everybody makes a big deal over I/O, "what if we change the database?" or "what if we switch from XML to JSON?" I think this is usually misguided. The real question should be "what if we need to change this int to a float, add a field, and change this String to a List of Strings?"
    – user949300
    Commented Mar 28, 2017 at 1:07
  • 2
    This is cheating. Single responsibility by itself is just a proposed way of "change isolation". Explaining, that you need to isolate changes to keep responsibility "single", does not suggest how to do this, just explains the origin of the requirement.
    – Basilevs
    Commented Mar 28, 2017 at 12:40
  • 6
    @Basilevs I'm trying to hone in the on the deficiency you're seeing in this answer--not to mention Uncle Bob's answer! But, perhaps I need to clarify that SRP is not about ensuring that "a change" will impact only 1 class. It's about ensuring that each class will respond to only "one change." ... It's about trying to draw your arrows from each class to a single owner. Not from each owner to a single class.
    – svidgen
    Commented Mar 28, 2017 at 13:50
  • 3
    Thank you for providing a pragmatic response! Even Uncle Bob warns against zealous adherence to SOLID principles in Agile Architecture. I don't have the quote handy, but he basically says that splitting responsibilities inherently increases the level of abstraction in your code and that all abstraction comes at a cost, so make sure the benefit of following SRP (or other principles) outweighs the cost of adding more abstraction. (cont'd next comment)
    – Michael L.
    Commented Mar 29, 2017 at 15:16
  • 4
    This is why we should put the product in front of the customer as early and as often as is reasonable, so they will force changes in our design and we can see what areas are likely to change in that product. Additionally, he warns that we can't protect ourselves from every kind of change. For any application, certain kinds of changes will be difficult to make. We need to make sure those are the changes which are least likely to happen.
    – Michael L.
    Commented Mar 29, 2017 at 15:16
32

I follow "classes should have only one reason to change".

For me, this means thinking of harebrained schemes that my product owner might come up with ("We need to support mobile!", "We need to go to the cloud!", "We need to support Chinese!"). Good designs will limit the impact of these schemes to smaller areas, and make them relatively easy to accomplish. Bad designs mean going to a lot of code and making a bunch of risky changes.

Experience is the only thing I've found to properly evaluate the likelihood of those crazy schemes - because making one easy might make two others harder - and evaluating the goodness of a design. Experienced programmers can imagine what they'd need to do to change the code, what is lying around to bite them in the ass, and what tricks make things easy. Experienced programmers have a good gut feel for how screwed they are when the product owner asks for crazy stuff.

Practically, I find that unit tests help here. If your code is inflexible, it's going to be hard to test. If you can't inject mocks or other test data, you're probably not going to be able to inject that SupportChinese code.

Another rough metric is the elevator pitch. Traditional elevator pitches are "if you were in an elevator with an investor, can you sell him on an idea?". Startups need to have simple, short descriptions of what they're doing - what their focus is. Likewise, classes (and functions) should have a simple description of what they do. Not "this class implements some fubar such that you can use it in these specific scenarios". Something you can tell another developer: "This class creates users". If you can't communicate that to other developers, you're going to get bugs.

5
  • Sometimes you go to implement what you thought would be a messy change, and it turns out simple, or a small refactor makes it simple, and adds useful functionality at the same time. But yeah, usually you can see trouble coming.
    – user251748
    Commented Mar 28, 2017 at 14:24
  • 18
    I'm a big advocate for the "elevator pitch" idea. If it's hard to explain what a class does in a sentence or two you are in risky territory.
    – Ivan
    Commented Mar 28, 2017 at 14:55
  • 1
    You touch on an important point: the likelihood of those crazy schemes varies dramatically from one project owner to the next. You have to rely not only on your general experience, but on how well you know the project owner. I've worked for people who wanted to cut our sprints down to one week, and still couldn't avoid changing direction mid-sprint. Commented Mar 28, 2017 at 16:41
  • 2
    In addition to the obvious benefits, documenting your code using "elevator pitches" also serves to help you think of what your code is doing using natural language which I find useful in uncovering multiple responsibilities.
    – Alexander
    Commented Mar 28, 2017 at 19:28
  • 1
    @KevinKrumwiede That is what the "Chicken running around with its head cut off" and "Wild Goose Chase" methodologies are for!
    – user251748
    Commented Mar 28, 2017 at 23:00
29

No one knows. Or at least, we are unable to agree on one definition. That is what makes SPR (and other SOLID principles) quite controversial.

I would argue being able to figure out what is or isn't a responsibility is one of the skills software developer has to learn over course of his career. The more code you write and review, the more experience you will have to determine if something is single or multiple responsibilities. Or if single responsibility is fractured across separate parts of the code.

I would argue that primary purpose of SRP is not to be hard rule. It is to remind us to be mindful of cohesion in code and to always put some conscious effort into determining what code is cohesive and what isn't.

5
  • 21
    New programmers do have a tendency to treat SOLID as if it were a set of laws, which it isn't. It's merely a grouping of good ideas to help people get better at class design. Alas, people tend to take these principles far too seriously; I recently saw a job posting that cited SOLID as one of the job requirements. Commented Mar 27, 2017 at 22:40
  • 10
    +42 for the last paragraph. As @RobertHarvey says, things like SPR, SOLID and YAGNI should not be taken as "absolute rules", but as general principals of "good advice". Between them (and others) the advice will sometimes be contradictory, but balancing that advice (as opposed to following a rigid set of rules) will (over time, as your experience grows) guide you to produce better software. There should be only one "absolute rule" in software development: "There are no absolute rules".
    – TripeHound
    Commented Mar 28, 2017 at 11:11
  • This is very good clarification on one aspect of SRP. But, even if the SOLID principles are not hard rules, they're not terribly valuable if no one understands what they mean--even less so if your claim that "no one knows" is actually true! ... it makes sense for them to be hard to understand. As with any skill, there's something that distinguishes the good from the less good! But ... "no one knows" makes it more of a hazing ritual. (And I don't believe that's the intent of SOLID!)
    – svidgen
    Commented Mar 28, 2017 at 14:10
  • 3
    By "No one knows", I hope @Euphoric simply means that there is no precise definition that will work for every use case. It is something that requires a degree of judgment. I think one of the best ways to determine where your responsibilities lay is to iterate rapidly and let your codebase tell you. Look for "smells" that your code is not easily maintainable. For instance, when a change to a single business rule starts having cascading affects through seemingly unrelated classes, you probably have a violation of SRP.
    – Michael L.
    Commented Mar 29, 2017 at 15:29
  • 2
    I heartily second @TripeHound and others who have pointed out that all these "rules" don't exist to define the One True Religion of development, but to increase the likelihood of developing maintainable software. Be very wary of following a "best practice" if you can't explain how it promotes maintainable software, improves quality or increases efficiency of development..
    – Michael L.
    Commented Mar 29, 2017 at 15:32
6

In this conference at Yale, Uncle Bob gives this funny example:

Enter image description here

He says that Employee has three reasons to change, three sources of change requirements, and gives this humorous and tongue-in-cheek, but illustrative nonetheless, explanation:

  • If the CalcPay() method has an error and costs the company millions of US$, the CFO will fire you.

  • If the ReportHours() method has an error and costs the company millions of US$, the COO will fire you.

  • If the WriteEmmployee() method has an error that causes the erasure of a lot of data and costs the company millions of US$, the CTO will fire you.

So having three different C level execs potentially firing you for costly errors in the the same class means the class has too many responsibilities.

He gives this solution that solves the violation of SRP, but yet has to solve the violation of DIP which is not shown in the video.

Enter image description here

4
  • This example looks more like a class that has the wrong responsibilities. Commented Mar 28, 2017 at 23:50
  • 4
    @RobertHarvey When a class has too many responsabilities, it means that the extra responsabilities are the wrong responsabilities. Commented Mar 28, 2017 at 23:54
  • 6
    I hear what you're saying, but I don't find it compelling. There's a difference between a class having too many responsibilities and a class doing something it has no business doing at all. It may sound the same, but it isn't; counting peanuts isn't the same as calling them walnuts. It's Uncle Bob's principle and Uncle Bob's example, but if it were sufficiently descriptive, we wouldn't need this question at all. Commented Mar 28, 2017 at 23:57
  • @RobertHarvey, what is the difference? Those situations seem isomorphic to me. Commented Apr 1, 2017 at 18:45
5

I think the term "responsibility" is useful as a metaphor because it permits us to use the software to investigate how well the software is organized. In particular, I'd focus on two principles:

  • Responsibility is commensurate with authority.
  • No two entities should be responsible for the same thing.

These two principles let us dole out responsibility meaningfully because they play off each other. If you are empowering a piece of code to do something for you, it needs to have a responsibility for what it does. This causes the responsibility that a class might have to grow, expanding it's "one reason to change" to wider and wider scopes. However, as you make things wider, you naturally start to run into situations where multiple entities are responsible for the same thing. This is fraught with issues in real life responsibility, so surely it is an issue in coding as well. As a result, this principle causes scopes to narrow, as you subdivide the responsibility into un-duplicated parcels.

In addition to these two, a third principle seems reasonable:

  • Responsibility can be delegated

Consider a freshly minted program... a blank slate. At first, you only have one entity, which is the program as a whole. It is responsible for... everything. Naturally at some point you will start delegating responsibility to functions or classes. At this point, the first two rules come into play forcing you to balance that responsibility. The top level program is still responsible for the overall output, just like a manager is responsible for the productivity of their team, but each sub-entity has been delegated responsibility, and with it the authority to carry out that responsibility.

As an added bonus, this makes SOLID particularly compatible with any corporate software development one might need to do. Every company on the planet has some concept of how to delegate responsibility, and they don't all agree. If you delegate responsibility within your software in a way which is reminiscent of your company's own delegation, it's going to be much easier for future developers to come up to speed with how you do things at this company.

3
  • I'm not 100% sure this fully explains it. But, I think explaining "responsibility" with respect to "authority" is an insightful way to phrase it! (+1)
    – svidgen
    Commented Mar 28, 2017 at 21:21
  • Pirsig said, "You tend to build your problems in to the machine", which gives me pause.
    – user251748
    Commented Mar 28, 2017 at 23:03
  • @nocomprende You also tend to build your strengths into the machine. I'd argue that when your strengths and your weakness are the same things, that's when it gets interesting.
    – Cort Ammon
    Commented Mar 28, 2017 at 23:13
5

SRP is hard to get right. It is mostly a matter of assigning 'jobs' to your code and making sure each part has clear responsibilities. Like in real life, in some cases splitting the work among people can be quite natural, but in other cases it may be really tricky, especially if you do not know them (or the work).

I always recommend you just write simple code that works first, then refactor a little: You will tend to see how the code clusters naturally after a while. I think it is a mistake to force responsibilities before you know the code (or people) and the work to be done.

One thing you will notice is when the module starts doing too much and is hard to debug/maintain. This is the moment to refactor; what should the core job be and what tasks could be given to another module? For example, should it handle security checks and the other work, or should you do security checks elsewhere first, or will this make the code more complex?

Use too many indirections and it becomes a mess again ... as for other principles, this one will be in conflict with others, like KISS, YAGNI, etc. Everything is a matter of balance.

3
  • Isn't SRP just Constantine's Cohesion writ large? Commented Mar 28, 2017 at 10:45
  • You will naturally find those patterns if you code long enough, but you can speed up the learning by naming them and it helps with communication ... Commented Mar 28, 2017 at 12:25
  • @NickKeighley I think it is cohesion, not so much writ large, but looked at from another perspective.
    – sdenham
    Commented Mar 29, 2017 at 11:23
4

I think a better way of subdividing things than "reasons to change" is to start by thinking in terms of whether it would make sense to require that code which needs to do perform two (or more) actions should need to hold a separate object reference for each action, and whether it would be useful to have a public object which could do one action but not the other.

If the answers to both questions are yes, that would suggest the actions should be done by separate classes. If the answers to both questions are no, that would suggest that from a public standpoint there should be one class; if the code for that would be unwieldy, it may be internally subdivided into private classes. If the answer to the first question is no, but the second is yes, there should be a separate class for each action plus a composite class which includes references to instances of the others.

If one has separate classes for a cash register's keypad, beeper, numeric readout, receipt printer, and cash drawer, and no composite class for a complete cash register, then code which is supposed to process a transaction might end up accidentally getting invoked in a way that takes input from one machine's keypad, produces noise from a second machine's beeper, shows numbers on a third machine's display, prints a receipt on a fourth machine's printer, and pops a fifth machine's cash drawer. Each of those sub functions might usefully be handled by a separate class, but there should also be a composite class which joins them. The composite class should delegate as much logic to the constituent classes as possible, but should when practical wrap functions of its constituent components rather than requiring client code to access the constituents directly.

One could say that each class's "responsibility" is either to incorporate some real logic or else to provide a common attachment point for multiple other classes that do so, but what's important is to focus first and foremost on how client code should view a class. If it makes sense for client code to see something as a single object, then client code should see it as a single object.

3
  • This is sound advice. It might be worth pointing out that you divide responsibilities according to more criteria than only the SRP. Commented Mar 28, 2017 at 12:18
  • 1
    Car analogy: I don't need to know how much gas is in someone else's tank, or want to turn someone else's wipers on. (but that's the definition of the internet) (Shh! you'll ruin the story)
    – user251748
    Commented Mar 28, 2017 at 14:29
  • 1
    @nocomprende - "I don't need to know how much gas is in someone else's tank," - unless you are a teenager trying to decide which of the family's cars to "borrow" for your next trip... ;)
    – alephzero
    Commented Mar 30, 2017 at 11:33
3

A good article that explains the SOLID programming principles and gives examples of code both following and not following this principles is https://scotch.io/bar-talk/s-o-l-i-d-the-first-five-principles-of-object-oriented-design.

In the example relating to SRP he gives an example of a few shape classes (circle and square) and a class designed to calculate the total area of multiple shapes.

In his first example he creates the area calculating class and has it return his output as HTML. Later on he decides he wants to display it as JSON instead and has to change his area calculating class.

The problem with this example is that his area calculating class is responsible for calculating the area of shapes AND displaying that area. He then goes through a better way to do this using another class designed specifically for displaying areas.

This is a simple example (and more easily understood reading the article as it has code snippets) but demonstrates the core idea of SRP.

3

"Single responsibility principle" is perhaps a confusing name. "Only one reason to change" is a better description of the principle, but is still easy to misunderstand. We are not talking about say what causes objects to change state at runtime. We are taking about what could cause developers to have to change the code in the future.

Unless we are fixing a bug, the change will be due to a new or changed business requirement. You will have to think outside the code itself, and imagine what outside factors might cause requirements to change independently. Say:

  • Tax rates change due to a political decision.
  • Marketing decides to change names of all products
  • UI has to be redesigned to be accessible
  • The database is congested, so you need to do some optimizations
  • You have to accommodate a mobile app
  • and so on...

Ideally you want independent factors to affect different classes. E.g. since tax rates change independent of product names, changes should not affect the same classes. Otherwise you run the risk of a tax change introduction an error in product naming, which is the kind of tight-coupling you want to avoid with a modular system.

So don't just focus of what could change - anything might conceivably change in the future. Focus on what might independently change. Changes are typically independent if they are caused by different actors.

Your example with job titles is on the right track, but you should take it more literally! If marketing might cause changes to the code and finance might cause other changes, these changes should not affect the same code, since these are literally different job titles and therefore changes will happen independently.

To quote Uncle Bob who invented the term:

When you write a software module, you want to make sure that when changes are requested, those changes can only originate from a single person, or rather, a single tightly coupled group of people representing a single narrowly defined business function. You want to isolate your modules from the complexities of the organization as a whole, and design your systems such that each module is responsible (responds to) the needs of just that one business function.

So to sum up: A "responsibility" is catering to a single business function. If more than one actor could cause you to have to change a class, then the class probably breaks this principle.

1
  • According to his book "Clean Architecture" this is exactly right. Business rules should come from one source, and once source only. This means that HR, Operations and IT all need to cooperate formulating requirements in a "Single Responsibility". And that is the principle. +1 Commented Jul 23, 2018 at 19:46
1

From Robert C. Martins book Clean Architecture: A Craftsman's Guide to Software Structure and Design, published September 10 2017, Robert writes on page 62 the following:

Historically, the SRP has been described this way:

A module should have one, and only one, reason to change

Software systems are changed to satisfy users and stakeholders; those users and stakeholders are the "reason to change". that the principle is talking about. Indeed, we can rephrase the principle to say this:

A module should be responsible to one, and only one, user or stakeholder

Unfortunately, the word "user" and "stakeholder" aren't really the right word to use here. There will likely be more than one user or stakeholder who wants the system changed in the sane way. Instead we're really referring to a group - one or more people who require that change. We'll refer to that group as an actor.

Thus the final version of the SRP is:

A module should be responsible to one, and only one, actor.

So this is not about code. The SRP is about controlling the flow of requirements and business needs, which can only come from one soure.

2
  • I'm not sure why you're making the distinction that "it's not about code." Of course it's about code; this is software development. Commented Jul 23, 2018 at 20:15
  • @RobertHarvey My point is that the flow of requirements comes from one source, the actor. Users and Stakeholders are not into code, they are into business rules which come to us as requirements. So the SRP is a process to control these requirements, which to me isn't code. It's Software Development (!), but not code. Commented Jul 23, 2018 at 20:20
0

First of all, what you have is actually two separate problems: the problem of what methods to put in your classes, and the problem of interface bloat.

Interfaces

You have this interface:

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

Presumable, you have multiple classes that conform to the CustomerCRUD interface (otherwise an interface is unnecessary), and some function do_crud(customer: CustomerCRUD) that takes in a conforming object. But you have already broken the SRP: you have tied these four distinct operations together.

Let's say that later on you would to operate on database views. A database view has only the Read method available for it. But you want to write a function do_query_stuff(customer: ???) that operators transparently on either full-blown tables or views; it only uses the Read method, after all.

So create an interface

public Interface CustomerReader { public Customer Read(customerID: int) }

and factor your CustomerCrud interface as:

public interface CustomerCRUD extends CustomerReader
{
  public void Create(Customer customer);
  public void Update(Customer customer);
  public void Delete(int CustomerID);
}

But there's no end in sight. There could be objects which we can create but not update, etc. This rabbit hole is too deep. The only sane way to adhere to the single responsibility principle is to make all your interfaces contain exactly one method. Go actually follows this methodology from what I've seen, with the vast majority of interfaces containing a single function; if you want to specify an interface that contains two functions, you have to awkwardly create a new interface that combines the two. You soon get a combinatorial explosion of interfaces.

The way out of this mess is to use structural subtyping (implemented in e.g. OCaml) instead of interfaces (which are a form of nominal subtyping). We don't define interfaces; instead, we can simply write a function

let do_customer_stuff customer = customer.read ... customer.update ...

that calls whatever methods we like. OCaml will use type inference to determine that we can pass in any object that implements these methods. In this example, it would be determine that customer has type <read: int -> unit, update: int -> unit, ...>.

Classes

This solves the interface mess; but we still have to implement classes that contain multiple methods. For example, should we create two different classes, CustomerReader and CustomerWriter? What if we want to change how tables are read (e.g. we now cache our responses in redis before after fetching the data), but now how they're written? If you follow this chain of reasoning to its logical conclusion, you're lead inextricably to functional programming :)

4
  • 4
    "Meaningless" is a bit strong. I could get behind "mystical" or "Zen." But, not flat-out meaningless!
    – svidgen
    Commented Mar 27, 2017 at 22:18
  • Can you explain a bit more why structural subtyping is a solution? Commented Mar 27, 2017 at 23:04
  • @RobertHarvey Restructured my answer significantly
    – gardenhead
    Commented Mar 28, 2017 at 1:19
  • 4
    I use interfaces even when I have only a single class implementing it. Why? Mocking in unit-tests.
    – Eternal21
    Commented Mar 28, 2017 at 18:31
0

In my mind, the closest thing to a SRP that comes to my mind is a usage flow. If you don't have a clear usage flow for any given class it's probably your class has a design smell.

A usage flow would be a given method call succession that would give you an expected (thus testable) result. You basically define a class with the use cases it got IMHO, that's why all program methodology focuses on interfaces over implementation.

0

It is to achieve that multiple requirement changes, does not require your component to change.

But good luck understanding that at first glance, when you first hear about SOLID.


I see a lot of comments saying SRP and YAGNI can contradict eachother, but YAGNI enforced by TDD (GOOS, London School) taught me to think of and design my components from a client's perspective. I started designing my interfaces by what is the least any client would want it to do, that is how little it should do. And that exercise can be done without any knowledge of TDD.

I like the technique described by Uncle Bob (I cannot remember from where, sadly), which goes something like:

Ask yourself, what does this class do?

Did your answer contain either of And or Or

If so, extract that part of the answer, it a responsibility of its own

This technique is an absolute, and as @svidgen said, SRP is a judgement call, but when learning something new, absolutes are the best, it is easier to just always do something. Make sure the reason you don't separate is; an educated estimation, and not because you don't know how to. This is the art, and it takes experience.


I think a lot of the answers seem to make an argument for decoupling when talking about SRP.

SRP is not to make sure a change doesn't propagate down the dependency graph.

Theoretically, without SRP, you wouldn't have any dependecies...

One change should not cause a change many places in the application, but we got other principles for that. SRP does however, improve the Open Closed Principle. This principle is more about abstraction, however, smaller abstractions are easier to reimplement.

So when teaching SOLID as a whole, be careful to teach that SRP allows you to change less code when requirements change, when in fact, it allows you to write less new code.

3
  • 3
    When learning something new, absolutes are the best, it is easier to just always do something. -- In my experience, new programmers are far too dogmatic. Absolutism lead to non-thinking developers and cargo-cult programming. Saying "just do this" is fine, so long as you understand that the person you are speaking to will have to later unlearn what you have taught them. Commented Mar 29, 2017 at 14:45
  • @RobertHarvey, Completely true it creates dogmatic behavior, and you have to unlearn/relearn as you gain experience. This is my point though. If a new programmer tries to make judgement calls without any way to reason their decision it seems borderline useless, because they don't know why it worked, when it worked. By making people overdo it, it teaches them to look for the exceptions instead of making unqualified guesses. Everything you said is about absolutism is correct, which is why it should only be a starting point. Commented Mar 30, 2017 at 12:23
  • @RobertHarvey, A quick real life example: You might teach your children to always be honest, but as they grow older, they will probably realize a few exceptions where people don't want to hear their most honest thoughts. To expect a 5 year old to make a correct judgement call about being honest is optimistic at best. :) Commented Mar 30, 2017 at 12:26
0

There is no clear answer to that. Although the question is narrow, the explanations aren't.

For me, it is something like Occam's Razor if you want to. It is an ideal where I try to measure my current code against. It is hard to nail it down in plain and simple words. Another metaphor would be »one topic« which is as abstract, i.e. hard to grasp, as »single responsibility«. A third descripton would be »dealing with one level of abstraction«.

What does that mean practically?

Lately I use a style of coding which consists mostly of two phases:

Phase I is best described as creative chaos. In this phase I write down code as thoughts are flowing - i.e. raw and ugly.

Phase II is the complete opposite. It is like cleaning up after a hurricane. This takes the most work and discipline. And then I look at the code from a designer's perspective.

I am working mostly in Python now, which allows me to think of objects and classes later. First Phase I - I write only functions and spread them almost by random in different modules. In Phase II, after I got things going, I have a closer look at what module deals with which part of the solution. And while skimming through the modules, topics are emergent to me. Some functions are thematically related. These are good candidates for classes. And after I turned functions into classes - which is nearly done with indentation and adding self to the parameter list in python ;) - I use SRP like Occam's Razor to strip out functionality to other modules and classes.

A current example may be writing small export functionality the other day.

There was the need for csv, excel and combined excel sheets in a zip.

The plain functionality was each done in three views (=functions). Each function used a common method for determining filters and a second method to retrieve the data. Then in each function the preparation of the export took place and was delivered as a Response from the server.

There were too many levels of abstraction mixed up:

I) dealing with incoming/outgoing request/response

II) determining filters

III) retrieving data

IV) transformation of data

The easy step was to use one abstraction (exporter) to deal with the layers II-IV in a first step.

The only remain was the topic dealing with requests/responses. At the same level of abstraction is extracting request parameters which is okay. So I had for this view one "responsibility".

Second, I had to break up the exporter, which as we saw consisted of at least three other layers of abstraction.

Determining filter criteria and actual retrival are nearly on the same level of abstraction (the filters are needed to get the right subset of the data). These levels were put into something like an data access layer.

In the next step I broke the actual export mechanisms apart: Where writing to a temporal file was needed, I broke that into two "responsibilities": one for the actual writing of the data to disk and another part which dealt with the actual format.

Along the forming of the classes and modules, things became clearer, what belonged where. And always the latent question, whether the class does too much.

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

It is hard to give a recipe to follow. Of course I could repeat the cryptic »one level of abstraction« - rule if that helps.

Mostly for me it is a kind of "artistic intuition" which leads to the current design; I model code like an artist may sculpt clay or does painting.

Imagine me as a Coding Bob Ross ;)

0

What I try to do to write code that follows the SRP:

  • Choose a specific problem you need to solve;
  • Write code that solves it, write everything in one method (eg: main);
  • Carefully analyze the code and, based on the business, try to define the responsibilities that are visible in all operations that are being performed (this is the subjective part that also depends on the business/project/customer);
  • Please, note that all the functionality is already implemented; what's next is only organization of the code (no additional feature or mechanism will be implemented from now on in this approach);
  • Based on the responsibilities that you defined in previous steps (that are defined based on the business and the "one reason to change" idea), extract a separate class or method for each one;
  • Please note that this approach only cares about the SPR; ideally there should be additional steps here trying to adhere to the other principles as well.

Example:

Problem: get two numbers from the user, calculate their sum and output the result to the user:

//first step: solve the problem right away
static void Main(string[] args)
{
    Console.WriteLine("Number 1: ");
    int firstNumber = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Number 2: ");
    int secondNumber = Convert.ToInt32(Console.ReadLine());

    int result = firstNumber + secondNumber;

    Console.WriteLine("Hi there! The result is: {0}", result);

    Console.ReadLine();
}

Next, try to define responsibilities based on tasks that need to be performed. From this, extract the appropriate classes:

//Responsible for getting two integers from the user
class Input {
    public int FirstNumber { get; set; }
    public int SecondNumber { get; set; }
    public void Read() {
        Console.WriteLine("Number 1: ");
        FirstNumber = Convert.ToInt32(Console.ReadLine());

        Console.WriteLine("Number 2: ");
        SecondNumber = Convert.ToInt32(Console.ReadLine());
    }
}

//Responsible for calculating the sum of two integers
class SumOperation {
    public int Result { get; set; }
    public void Calculate(int a, int b) {
        Result = a + b;
    }
}

//Responsible for the output of some value to the user
class Output {
    public void Write(int result) {
        Console.WriteLine("Hello! The result is: {0}", result);
    }
}

Then, the refactored program becomes:

//Program: responsible for main execution.
//Gets two numbers from user and output their sum.
static void Main(string[] args)
{
    var input = new Input();
    input.Read();

    var operation = new SumOperation();
    operation.Calculate(input.FirstNumber, input.SecondNumber);

    var output = new Output();
    output.Write(operation.Result);

    Console.ReadLine();
}

Note: this very simple example takes into consideration only the SRP principle. The usage of the other principles (eg: the "L" -- code should depend on abstractions rather than concretions) would provide more benefits to the code and make it more maintainable for business changes.

2
  • 2
    Your example is too simple to adequately illustrate SRP. Nobody would do this in real life. Commented Oct 4, 2017 at 15:06
  • Yes, in real projects I write some pseudocode rather than writing the the exact code like in my example. After the pseudocode, I try to split the responsibilities just like I did in the example. Anyway, that's just how I do it. Commented Oct 4, 2017 at 16:39

Not the answer you're looking for? Browse other questions tagged or ask your own question.