in reply to Definition of Law of Demeter
in thread Are lvalue methods (as properties) a good idea?

As I understand it, the LoD is about not spreading knowledge (about the relationship between objects) all over the place.

Let's say object A has a method foo() that does something useful. Object a (instance-of class A) has-an object b that implements the useful thing.

So the call chain is a->b->foo(). But that exposes the fact that a has-a b, when in fact that information could be private to class A. According to the LoD, the call chain would be a->foo(), and that foo() would be implemented as b->foo().

Taken to an extreme, this is the “bloated middlemen classes full of forwarding methods.” that Aristotle talks about in Re^3: Are lvalue methods (as properties) a good idea?. If large parts of the interface of B is duplicated in A, what's the point? Isn't A and B pretty tightly coupled anyway?

On the other hand... What if the call chain is a->b->c->d->foo(), and you type this monster at 108 different places in your program? Wouldn't it be a bit nicer to type a->foo() and encapsulate the knowledge of b->c->d->foo() so it doesn't break at 108 different places if any of b, c, or d changes?

This is why the LoD shouldn't be a "law" to be followed at all times, but a rather a guideline. Or as the Pragmatic guys phrased it in an article I read somewhere: The Pretty Good Idea of Demeter.

(hey, I just found it. The Art of Enbugging. It's really good and, of course, explains it a lot better than I can).

IMHO,

/J

  • Comment on Re: The Pretty Good Idea of Demeter (Was: Definition of Law of Demeter)

Replies are listed 'Best First'.
Re^2: The Pretty Good Idea of Demeter (Was: Definition of Law of Demeter)
by fergal (Chaplain) on Jan 16, 2005 at 13:07 UTC
    To me, the a->b->c->d->foo() example just tells me that this needs to be factored out into a routine somewhere. Putting it in a's class is not necessarily the right thing, perhaps it just needs to be in a function local to the module that uses 108 times (of course if those 108 uses are spread all over then it probably should be in a's class).

    Choosing the correct class to put it in may be difficult. It may be useful to call x->b->c->d->foo() for lots of different xs that don't necessarily share a base class and adding in a mixin class to all those classes may not be not possible.

    Many examples of LoD are really about removing accessors. I think it's better to replace a->mother->brother->child->foo() with a->cousin->foo(). Do not replace it with a->foo_the_cousin() because chances are you'll want to call other methods on the cousin and also because it's not much of a saving in terms of typing (in this case it's not a saving at all).

    So my sugestion for a better LoD is Consider replacing chains of accessor methods with a single method

      Depends. In other cases it might still be appropriate to replace a->b->foo with a->foo. The question is: does the caller need to be aware of the has-a relationship, or is he really just requesting a service from object a? In that case, the presence of object b would be an implementation detail, and shouldn't be exposed just because b is also an object.

      The lesson taught by Demeter is really don't expose your inner objects without good reason. I believe what came to be called the Law was formed in the context of larger class frameworks. In this environment, I would guess, people are likely not to think about distinguishing between public and private class relationships. A reminder is then necessary that relationships are part of the published interface of a class no less than methods and properties are, and so require equal consideration.

      Makeshifts last the longest.

      I like your example with mother->brother->child and cousin. Not because it reduces the amount of keystrokes, but because it adds abstraction and meaning.

      This may be the real gift of the LoD, that when applied properly it's an opportunity to clarify the intentions and the meaning of the relationship between mother, brother, and child.

      /J