26
\$\begingroup\$

Inspired by this question, but hopefully not a duplicate.

I understand that the Law of Demeter is very useful in case of services interacting with other services, for example it's much easier to mock the other services in unit tests. What about services interacting with data models, though?

Let's say I have a hierarchy of immutable classes:

public final class Document {
    private final List<Page> pages;
    // Constructor, getter, ...
}

public final class Page {
    private final List<Paragraph> paragraphs;
    // Constructor, getter, ...
}

public final class Paragraph {
    private final List<Line> lines;
    // Constructor, getter, ...
}

Let's say I want to do something with certain lines in a document doc:

public void doSomething(Document doc) {
    for (Page page : doc.getPages()) {
        for (Paragraph para : page.getParagraphs()) {
            for (Line line : para.getLines()) {
                if (someCondition(line)) {
                    someAction(line);
                }
            }
        }
    }

Now, as far as I understand, the above code doesn't obey the Law of Demeter. However, I don't want to force the law at all cost and clutter the model (here, the Document class) with tens of methods like:

public List<Line> filterWrtSomeCondition();
public List<Line> filterWrtSomeOtherCondition();

Or maybe there's a third way? I suspect that the Law of Demeter applies primarily to services interacting with other services. Is it a valid interpretation?

\$\endgroup\$
16
\$\begingroup\$

For some perspective on the LoD, read "The Law of Demeter Is Not A Dot Counting Exercise". In particular the part under the heading "I Fought The Law and The Law Won".

I think in this instance that pages, paragraphs and lines are very logical subcomponents of documents. These each have different but related behaviour. The each also serve different purposes.

Forcing the document to handle interactions with subcomponents will - as you say - lead to clutter (and more chance of introducing troublesome issues).

The big thing about the LoD is that it aims to reduce coupling. However adding methods to Document doesn't really reduce coupling as you're still writing code that says "do x to line y" - you're just asking Document to do it for you.

Don't get me wrong, the LoD is useful. But like all principles it has to be used correctly to stay useful.

(Funnily enough a similar question with a similar answer as been asked at SO.)


Conclusion

In short I don't see any benefit for you in talking to Document rather than it's contained Pages, Paras, & Lines. Frankly I don't think there's any ROI for you.

\$\endgroup\$
1
  • 3
    \$\begingroup\$ Interesting reference, especially: "If LoD is about encapsulation (aka information hiding) then why would you hide the structure of an object where the structure is exactly what people are interested in and unlikely to change?". I tend to think that OO design principles intended for objects with responsibilities and behaviors should be left aside when working with data structures, which are really a different kind of objects. \$\endgroup\$ – Eric Bréchemier Jan 22 '11 at 8:52
12
\$\begingroup\$

I'll argue the counterpoint ...

The LoD would say that this client code is being tied to a particular model of the Document. In the end, the code is really interested in the lines of a document. How reasonable is it that we tie that code to the getPage / getParagraph / getLine model?

Its perfectly reasonable to believe that pages wills always be made up of paragraphs and paragraphs made up of lines, but I don't think this is the only consideration. The other question to ask is what other models should Document support? Stated differently:

"Is it reasonable to look at a Document as a series of Lines without regard to what page or paragraph they are in?"

I think it is, so I would give Document a getLines() method and not force our code to go through the Page/Paragraph/Line model.

This getLines() method is also a good thing, because: if it its reasonable for our Document's client to see things this way, it is reasonable that Document may see things this way too. That is, isn't it possible that the Document may have an efficient structure for moving from line to line (if not now, then perhaps in the future) that may not involve pages and paragraphs? If so, forcing client code to go through the getPage / getParagraph / getLine is hurting the opportunity to use that efficient structure.

I also think its unreasonable "Forcing the document to handle interactions with subcomponents" in every conceivable case, so there has to be a happy medium out there. As a counter example, consider:

"is it reasonable to look at a Library as a series of Lines without regard to what document they are in?"

I don't think I'll be providing a getLines() method to my Library class.

\$\endgroup\$
0
3
\$\begingroup\$

I also agree that this code violates LoD. The caller is tying the implementation down to a model of nested objects.

Adding a bunch of filter methods also doesn't seem like the right idea, but this is an opportunity in disguise. You really want a general filtering method that can take a "verb" to apply to the matching elements. Or, alternatively, a filter method that takes a predicate and returns just the collection of matching elements.

This is one of the useful things about LoD. It helps you uncover places where you are operating at a low level of abstraction, and raise the level. In this case, once you introduce general predicates or verbs, you'll be very likely to find other places to use the same idiom. What begins as a local cleanup can uncover a very general mode of expression.

\$\endgroup\$
2
\$\begingroup\$

I'd say that it isn't cluttering the model personally. You are actually protecting yourself by not exposing internal implementation of your document class. The entire idea is to put all of your behavior into your class instead of having external code modify the class in such a way that it may not pass its own internal invariants.

Perhaps it also would make sense to make subclasses of your Document class which are specialized and only have the methods related to that usage of your Document. You don't have to have a one stop shop where you can do absolutely everything, since that's probably not going to be your primary use case in all instances.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks for your answer! Although I didn't stress that, I've mentioned that the model is immutable, so there is no way to "break" it once it's created. Does that change anything? \$\endgroup\$ – Bolo Jan 21 '11 at 17:41
  • 2
    \$\begingroup\$ I'm skeptical of adding subclasses, though. For one, I can clearly name and document the Document class, but how do I name a Document subclass with methods for evaluating text readability, or with methods for finding occurrences of a given phrase? As in: "If you can't name it, it shouldn't be a class." \$\endgroup\$ – Bolo Jan 21 '11 at 17:42
  • \$\begingroup\$ Didn't catch on to the immutable part. It at least makes it so you can't break it, but exposing the internal implementation is still valid. Without knowing your use case, I'm just guessing and saying that subclasses may be a valid way to address your concern of adding many methods (they may be broken up). \$\endgroup\$ – Mark Loeser Jan 21 '11 at 17:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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