Coupling, Cohesion and the Law of Demeter
Asked Answered
R

6

70

The Law of Demeter indicates that you should only speak to objects that you know about directly. That is, do not perform method chaining to talk to other objects. When you do so, you are establishing improper linkages with the intermediary objects, inappropriately coupling your code to other code.

That's bad.

The solution would be for the class you do know about to essentially expose simple wrappers that delegate the responsibility to the object it has the relationship with.

That's good.

But, that seems to result in the class having low cohesion. No longer is it simply responsible for precisely what it does, but it also has the delegates that in a sense, making the code less cohesive by duplicating portions of the interface of its related object.

That's bad.

Does it really result in lowering cohesion? Is it the lesser of two evils?

Is this one of those gray areas of development, where you can debate where the line is, or are there strong, principled ways of making a decision of where to draw the line and what criteria you can use to make that decision?

Religieux answered 2/10, 2008 at 15:40 Comment(2)
Whilst a little off topic I would strongly recommend Ted Faisons "Event-based programming: taking events to the limit" link. It gives a great breakdown on the common coupling issues and gives a good system of giving a measurement to the amount of coupling in a given systemBrassiere
When violating the LoD, whether the additional linkages are "improper" depends on the nature of the linked classes: If those are very prominent (think Row, Column, Cell in the implementation of Excel), the coupling to them is unproblematic and the LoD is overly restrictive. Likewise if the classes in question are purposefully mere data classes with essentially no behavior.Heirdom
D
50

Grady Booch in "Object Oriented Analysis and Design":

"The idea of cohesion also comes from structured design. Simply stated, cohesion measures the degree of connectivity among the elements of a single module (and for object-oriented design, a single class or object). The least desirable form of cohesion is coincidental cohesion, in which entirely unrelated abstractions are thrown into the same class or module. For example, consider a class comprising the abstractions of dogs and spacecraft, whose behaviors are quite unrelated. The most desirable form of cohesion is functional cohesion, in which the elements of a class or module all work together to provide some well-bounded behavior. Thus, the class Dog is functionally cohesive if its semantics embrace the behavior of a dog, the whole dog, and nothing but the dog."

Subsitute Dog with Customer in the above and it might be a bit clearer. So the goal is really just to aim for functional cohesion and to move away from coincidental cohesion as much as possible. Depending on your abstractions, this may be simple or could require some refactoring.

Note cohesion applies just as much to a "module" than to a single class, ie a group of classes working together. So in this case the Customer and Order classes still have decent cohesion because they have this strong relationshhip, customers create orders, orders belong to customers.

Martin Fowler says he'd be more comfortable calling it the "Suggestion of Demeter" (see the article Mocks aren't stubs):

"Mockist testers do talk more about avoiding 'train wrecks' - method chains of style of getThis().getThat().getTheOther(). Avoiding method chains is also known as following the Law of Demeter. While method chains are a smell, the opposite problem of middle men objects bloated with forwarding methods is also a smell. (I've always felt I'd be more comfortable with the Law of Demeter if it were called the Suggestion of Demeter .)"

That sums up nicely where I'm coming from: it is perfectly acceptable and often necessary to have a lower level of cohesion than the strict adherence to the "law" might require. Avoid coincidental cohesion and aim for functional cohesion, but don't get hung up on tweaking where needed to fit in more naturally with your design abstraction.

Declension answered 3/10, 2008 at 5:47 Comment(6)
I like this response, it does seem to imply it is a grey thing. So, if I may paraphrase, would you argue that as long as the you maintain high cohesion in the objects involved, that it is acceptable to violate the Law of Demeter?Religieux
Absolutely, that's a good way of putting it. BTW, have you seen JQuery, chaining of method calls 3+ levels is common. Although it might be in a different context (more procedural) someone coming from JQuery to say Java/C#/C++ is going to have all sorts of bad habits to "unlearn"!Declension
If I can suggest you add this point to your post (about it being okay to violate the Law of Demeter)...right now it's the best response I've seen.Religieux
Another source I just happened across. Search for Demeter on the following page, and include it in your response... martinfowler.com/articles/mocksArentStubs.htmlReligieux
Bradley, done, good quote by the way. Hopefully this will be useful for plenty of others too.Declension
About the last paragraph, shouldn't it be "avoid COincidental cohesion"?Corking
F
21

If you are violating the Law of Demeter by having

int price = customer.getOrder().getPrice();

the solution is not to create a getOrderPrice() and transform the code into

int price = customer.getOrderPrice();

but instead to note that this is a code smell and make the relevant changes that hopefully both increase cohesion and lower coupling. Unfortunately there is no simple refactoring here that always applies, but you should probably apply tell don't ask

Forsooth answered 2/10, 2008 at 15:53 Comment(2)
The link to "tell, don't ask" is well worth the read. Thank you!Adolfo
"Tell, don't ask" also gets you closer to abstractions in the problem domain (user requirements) as opposed to implementation details (design or derived requirements).Alric
H
6

I think you may have misunderstood what cohesion means. A class that is implemented in terms of several other classes does not necessarily have low cohesion, as long as it represents a clear concept, and has a clear purpose. For example, you may have a class Person, which is implemented in terms of classes Date (for date of birth), Address, and Education (a list of schools the person went to). You may provide wrappers in Person for getting the year of birth, the last school the person went to, or the state where he lives, to avoid exposing the fact that Person is implemented in terms of those other classes. This would reduce coupling, but it would make Person no less cohesive.

Highjack answered 2/10, 2008 at 16:0 Comment(0)
L
4

It’s a grey area. These principals are meant to help you in your work, if you find you’re working for them (i.e. they’re getting in your way and/or you find it over complicates your code) then you’re conforming too hard and you need to back off.

Make it work for you, don’t work for it.

Luhe answered 2/10, 2008 at 16:1 Comment(1)
this is a good description for Law of Demeter "Make it work for you, don’t work for it."Alisander
D
1

I don't know if this actually lowers cohesion.

Aggregation/composition are all about a class utilising other classes to meet the contract it exposes through its public methods. The class does not need to duplicate the interface of it's related objects. It's actually hiding any knwowledge about these aggregated classes from the method caller.

To obey the law of Demeter in the case of multiple levels of class dependency, you just need to apply aggregation/composition and good encapsulation at each level.

In other words each class has one or more dependencies on other classes, however these are only ever dependencies on the referenced class and not on any objects returned from properies/methods.

Declension answered 2/10, 2008 at 16:0 Comment(4)
To use another posters example, though, if a customer object now needs to know not only about the customer's order, but the price of the customer's order, then that's mucking up the interface of the customer in a way that would seem to lower cohesion.Religieux
It's also about the level/type of abstraction being used. A Customer, by definition, must have some close association with an Order/s. Therefore the cohesiveness of a Customer is not reduced by the customer.getOrderPrice(); refactoring. eg: Customer could use a simple decimal as orderPrice.Declension
How does that not reduce cohesiveness? It's not related to the Customer, it's related to the Order. You're just exposing it at the level of the Customer. When does it start reducing cohesiveness? Does customer.getOrderAddressStreet() cause a reduction in cohesion? Maybe it's all gray....Religieux
In the canonical situation, Orders are created by Customers. An Order without a Customer is not valid. If somethng is realted to an Order it is also related to the Customer that created that order. Customer and Order work together to provide some well bounded bahaviour. See my additional answer.Declension
T
0

In the situations where there seems to be a tradeoff between coupling and cohesion, I'd probably ask myself "if somebody else had already written this logic, and I were looking for a bug in it, where would I look first?", and write the code that way.

Thankful answered 2/10, 2008 at 15:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.