문제

The Single Responsibility Principle states that a class should do one and only one thing. Some cases are pretty clear cut. Others, though, are difficult because what looks like "one thing" when viewed at a given level of abstraction may be multiple things when viewed at a lower level. I also fear that if the Single Responsibility Principle is honored at the lower levels, excessively decoupled, verbose ravioli code, where more lines are spent creating tiny classes for everything and plumbing information around than actually solving the problem at hand, can result.

How would you describe what "one thing" means? What are some concrete signs that a class really does more than "one thing"?

도움이 되었습니까?

해결책

I really like the way Robert C. Martin (Uncle Bob) restates the Single Responsibility Principle (linked to PDF):

There should never be more than one reason for a class to change

It's subtly different from the traditional "should do only one thing" definition, and I like this because it forces you to change the way you think about your class. Instead of thinking about "is this doing one thing?", you instead think about what can change and how those changes affect your class. So for example, if the database changes does your class need to change? What about if the output device changes (for example a screen, or a mobile device, or a printer)? If your class needs to change because of changes from many other directions, then that's a sign that your class has too many responsibilities.

In the linked article, Uncle Bob concludes:

The SRP is one of the simplest of the principles, and one of the hardest to get right. Conjoining responsibilities is something that we do naturally. Finding and separating those responsibilities from one another is much of what software design is really about.

다른 팁

I keep asking myself what problem is SRP trying to solve? When does SRP help me? Here’s what I came up with:

You should refactor responsibility / functionality out of a class when:

1) You’ve duplicated functionality (DRY)

2) You find that your code needs another level of abstraction in order to help you understand it (KISS)

3) You find that pieces of functionality are understood by your domain experts as being apart of a different component (Ubiquitous Language)

You SHOULDN’T refactor responsibility out of a class when:

1) There isn’t any duplicated functionality.

2) The functionality doesn’t make sense outside of the context of your class. Put another way, your class provides a context in which it is easier to understand the functionality.

3) Your domain experts don’t have a concept of that responsibility.

It occurs to me that if SRP is applied to broadly, we trade one kind of complexity (trying to make heads or tails of a class with way too much going on inside) with another kind (trying to keep all of the collaborators / levels of abstraction straight in order to figure out what these classes actually do).

When in doubt, leave it out! You can always refactor later, when there is a clear case for it.

What do you think?

I don't know if there is an objective scale to it but what would give this away would be the methods - not so much the number of them but the variety of their function. I agree you can take decomposition too far but I wouldn't follow any hard and fast rules about it.

The Answer lies in the definition

What you define the responsibility to be, ultimately gives you the boundary.

Example:

I have a component that has the responsibility of displaying invoices -> in this case, if I started to add anything more then I'm breaking the principle.

If on the other hand if I said responsibility of handling invoices -> adding multiple smaller functions (eg printing Invoice, updating Invoice) all are within that boundary.

However if that module started to handle any functionality outside of invoices, then it would be outside of that boundary.

I always view it on two levels:

  • I make sure my methods only do one thing and do it well
  • I see a class as a logical (OO) grouping of those methods that represents one thing well

So something like a domain object called Dog:

Dog is my class but Dogs can do many things! I might have methods such as walk(), run() and bite(DotNetDeveloper spawnOfBill) (sorry couldn't resist ;p).

If Dog becomes to unwieldy then I'd think about how groups of those methods can be modeled together in another class, such as a Movement class, which could contain my walk() and run() methods.

There's no hard and fast rule, your OO design will evolve over time. I try to go for a clear cut interface/public API as well as simple methods that do one thing and one thing well.

I look at it more along the lines of a class should only represent one thing. To appropriate @Karianna's example, I have my Dog class, which has methods for walk(), run() and bark(). I'm not going to add methods for meaow(), squeak(), slither() or fly() because those aren't things that dogs do. They are things that other animals do, and those other animals would have their own classes to represent them.

(BTW, if your dog does fly, then you should probably stop throwing him out of the window).

A class should do one thing when viewed at its own level of abstraction. It will doubtless do many things at a less abstract level. This is how classes work to make programs more maintainable: they hide implementation details if you don't need to examine them closely.

I use class names as a test for this. If I can't give a class a fairly short descriptive name, or if that name has a word like "And" in it, I'm probably violating the Single Responsibility Principle.

In my experience, it's easier to keep this principle at the lower levels, where things are more concrete.

It's about having one unique rôle.

Each class should be resumed by a role name. A role is in fact a (set of) verb(s) associated with a context.

For example :

File provide a file's access. FileManager manage File objects.

Resource hold data for one resource from a File. ResourceManager hold and provide all Resources.

Here you can see that some verbs like "manage" imply a set of other verbs. Verbs alone are better thought as functions than classes, most of the time. If the verb imply too much actions that have their own common context, then it should be a class in itself.

So, the idea is only to let you have a simple idea of what does the class by defining a unique role, that might be the agregate of several sub-role (performed by member objects or other objects).

I often build Manager classes that have several different other classes in it. Like a Factory, a Registry, etc. See a Manager class like some kind of group chief, a orchestra chief that guide other peoples to work together to achieve a high level idea. He have one role, but imply working with other unique roles inside. You can also see it like how a company is organized : a CEO isn't a productive one on the pure productivity level, but if he is not there, then nothing can work correctly together. That's his role.

When you design, identify unique roles. And for each role, again see if it can't be cut in several other roles. That way, if you need to simlpy change the way your Manager build objects, simply change the Factory and go with peace in mind.

The SRP is not just about dividing classes up, but also about delegating functionality.

In the Dog example used above, don't use SRP as justification to have 3 separate classes such as DogBarker, DogWalker, etc (low cohesion). Instead, look at the implementation of a class's methods and decide if they "know too much". You can still have dog.walk(), but probably the walk() method should delegate to another class the details of how walking is accomplished.

In effect we are allowing the Dog class to have one reason to change: because Dogs change. Of course, combining this with other SOLID principles you would Extend Dog for new functionality rather than changing Dog (open/closed). And you would Inject your dependencies such as IMove and IEat. And of course you would make these separate interfaces (interface segregation). Dog would only change if we found a bug or if Dogs fundamentally changed (Liskov Sub, don't extend and then remove behavior).

The net effect of SOLID is that we get to write new code more often than we have to modify existing code, and that is a big win.

It all depends on the definition of responsibility and how that definition is going to impact maintenance of your code. Everything boils down to one thing and that is how your design is going to help you in maintaining your code.

And Like someone said "Walking on water and designing software from given specs is easy, provided both are frozen" .

So, if we are defining responsibility in more concrete way, then we don't need to change it.

Sometimes responsibilities will be glaringly obvious, but sometimes it might be subtle and we need to judiciously decide.

Suppose, we add another responsibility to Dog class named catchThief(). Now it might be leading towards an additional different responsibility. Tomorrow, if the way Dog is catching thief has to be changed by Police Dept, then Dog class will have to be changed. It would be better to create another subclass in this case and name it ThiefCathcerDog. But in a different view, if we are sure that it is not going to change in any circumstance, or the way catchThief has been implemented is dependent on some external parameter, then it is perfectly ok to have this responsibility. If responsibilities are not strikingly odd then we have to decide it judiciously based on the use case.

"One reason to change" depends on who is using the system. Make sure you have you use cases for each actor, and make a list of most likely changes, for each possible change of use case make sure only one class would be affected by that change. If you are adding a entirely new use case, make sure you would only need to extend a class to do so.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 softwareengineering.stackexchange
scroll top