I’ve read several articles on SRP and cohesion, and they seem to contradict each other as far as low coupling is concerned.
Articles on cohesion argue that putting closely related responsibilities together in a class Highly_Cohesive_Class
reduces coupling, while articles on SRP would argue that we’d reduce coupling by removing these closely related responsibilities from class Highly_Cohesive_Class
into separate classes ( such that each class only has single responsibility/reason to change ).
Don’t the two claims contradict each other? Namely,
BTW – I’m aware of the fact that class adhering to SRP principle is also considered highly cohesive class, but in this post the term highly cohesive refers to a class that has several closely related responsibilities.
15
I think the confusion is that high cohesion does not necessarily mean you want to put all of the “closely related responsibilities” in one class, but rather that all of the items in a class should correspond to closely related responsibilities.
For example, if you have a Kitchen class, you wouldn’t want bathroom logic in it; however, you don’t HAVE to also have oven and kitchen sink logic in it too, just because they are related. Although it is a kitchen appliance, the Oven probably deserves its own class, and would end up relating to the Kitchen through composition. Likewise the KitchenSink would be related to the Kitchen through composition.
So, look at a highly cohesive as NOT HAVING unrelated logic in it, and look a SRP as the call to delegate responsibilities to objects that serve that one responsibility.
4
Uncle Bob states in his book that this might be one of the least understood principles. I’ve seen lots of evidence of that in my career.
From his clean code blog:
The Single Responsibility Principle
Another wording for the Single Responsibility Principle is:
Gather together the things that change for the same reasons. Separate those things that change for different reasons.
If you think about this you’ll realize that this is just another way to define cohesion and coupling. We want to increase the cohesion between things that change for the same reasons, and we want to decrease the coupling between those things that change for different reasons.However, as you think about this principle, remember that the reasons for change are people. It is people who request changes.
he also defines it as:
A module should be responsible to one, and only one, actor.
Yet another way to say this is that an SRP compatible component is one that when it does change it always changes with respect to its ability to fulfill a role for a specific audience. As a unit these functions satisfy a single need. Depending upon the language you use ‘component’ could mean a single package, class or file.
for example: consider a class that implements stove (from the post above) would have inputs (electricity/natural gas/propane) and outputs (BTUs and burner configurations and other features like self cleaning).
All of this stuff is related to the single job of cooking food and the cook is the single party that cares about combining these functions to achieve their goals. The SRP indicates that it is preferable to put all of these features in a single component. Some may be exposed and others completely internal.
The point is that there is very little reason to split these aspects away from the stove as they relate to the stove and to nothing else. Any cook can control these aspects through the stoves interface, the knobs and burners. Every cook wanting to use the stove will have exactly the same concerns and benefit from the interface in the same way. Because these functions are all inseparable from the stove (it isn’t as useful without them and they don’t apply to anything else) it is wise to collate them within it because they have high cohesion.
A frying pan on the other hand is a much smaller component that can be used with the stove but is not an integral part of it. When you replace the stove you don’t necessarily have to replace the frying pan.
The pan may have a simple interface like:
fry ( foodItem ); stop();
FoodItems might only have:
isDone();
So some components can be large and others small and where the line is drawn depends upon the component’s usage which is why I think there is so much confusion about the principle.
In an oversimplified but more pertinent coding-related example the one ‘person’ may be a developer that wants to access files or something like that. Here the single purpose is file I/O and the ‘audience’ is developers. They probably expect a useful file class to have several related functions on it like:
a = file.read()
file.seek(c)
file.write(b)
file.close()
These functions have high cohesion because they are intimately related to each other with respect to the file object the class represents. They make the file object highly useful and internal aspects of the file that the outside world need not be concerned with are encapsulated.
one could separate them into separate classes and pass a file object (or worse, the file name) into them. i.e. something like:
a = fileReader(file);
pos = fileSeeker(file, c);
fileWriter(file, b, pos);
fileCloser(file);
These objects “do one thing” but because they all operate on a file object and they are independent of other purposes splitting them up clutters the namespace for no gain. Note also that by turning the object ‘inside out’ the file position (pos) must now become an additional concern for the outside environment.
This supposed ‘application’ of the principle would really in fact be a ‘violation’ of it. There are many well-meaning but incorrect examples out there that would lead developers to the wrong conclusion. I think this is why Uncle Bob says it is one of the poorest understood of the SOLID principles.
It isn’t very cohesive to break up a component that performs interrelated operations on the same object(s). It should only be done if there are multiple classes of ‘consumers’ that depend upon the the same feature set but to achieve different ends.
1
You are mixing up the two. Responsibility is about behavior, about what a class should do or be capable of. Cohesion is more about components, about grouping classes that operate within the same domain. So you could have a class to contain some related data and another class to retrieve that type of data from a store. The data members in the class would be cohesive because they have little meaning on their own, they have meaning together (may come from the same table). And the classes could be in the same assembly because they both deal with offering access to data, which would make them cohesive.
Having separate classes for containing data and for fetching/storing data would comply to SRP. At the same time putting them in the same assembly would recognize their cohesiveness. You want to separate them yet keep them close together because they need each other.
The components in a car are highly cohesive, they work together. Yet it is a good thing the steering mechanics are separated from the gear box so you can maintain the two separately and modifying one will not impact the other. At the same time you as a driver want access to both when you drive so it is nice they are integrated in the same vehicle.