The Law of Demeter - am I the only one who misunderstood it?

Have you ever wondered what is your brain good at? Ask any neuroscientist and they’ll tell you it’s really good at thinking as little as possible. It makes perfect sense from the evolutionary point of view - it’s a very energy consuming process. That’s the reason why we start doing things automatically, we create habits. Recall how focused you had to be when riding a bike for the first time and compare it to how easy it is now - the riding part is automatic and you can focus on other things like talking to a friend. It also happens at work - just yesterday I saw a method call similar to this

order.parcel(123).products().first()

but because of the link that will be mentioned later let's slightly modify the example:

library.book(“title”).pages().first()

And what did I do? Without thinking too much, driven by the habit called “Law Of Demeter” (or rather my incorrect understanding of it) I refactored the code so at the end it looked like this:

library.firstPageOfBook(“title”)

And that aligns with what Wikipedia says about this law:

[...] For many modern object oriented languages that use a dot as field identifier, the law can be stated simply as "use only one dot".

But what would happen if we stick to this rule but a new functionality needs to retrieve the last page, some other has to delete the title page and yet another requires the n-th page of the book? Our class will grow and grow…

library.lastPageOfBook(“title”);
library.deleteTitlePageOfBook(“title”);
library.pageOfBook(“title”, pageNumber);

Looks like applying this rule forces us into having a lot of methods with long names thus making our objects bigger and less cohesive. And we obviously don’t want that (SRP and all that jazz).

Yegor Bugayenko’s Elegant Objects is, in my opinion, a great book. Even if you don’t agree with his view on OOP It makes you question a lot of your basic assumptions and habits. It’s this book's chapter 5.9 and the comments under his blog entry (goo.gl/ZVjkZ6) that triggered me to write this post.

What they say is that we can use objects returned by a method of another object but we

Must not allow our code to extract objects from objects and then use them.

Making it perfectly OK to ask an object to build something for us and do whatever we want with it, but preventing us from first looking into its class definition, then checking what fields does it have, accessing one of them and using it in our code. Doesn’t matter if it’s accessed directly or through a getter - both ways create tight coupling as we depend on object’s internal structure. So the Law is steering us away from a strangely popular understanding of OOP where most of the objects are just bags with data, void of any behaviour. In fact Robert C. Martin makes a point in Clean Code that LoD should not be applied to procedural code - it's a purely OOP concept.

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

Whether this is a violation of Demeter depends on whether or not ctxt, Options, and ScratchDir are objects or data structures. If they are objects, then their internal structure should be hidden rather than exposed, and so knowledge of their innards is a clear violation of the Law of Demeter. On the other hand, if ctxt, Options, and ScratchDir are just data structures with no behavior, then they naturally expose their internal structure, and so Demeter does not apply.

Let me try to explain it with an example. Suppose a reader wants to get a book from a library and read it page by page:

public class Reader {

  private final Library library;

  void readAboutOop() {
      String content = library.book("OOP").pages().stream()
              .filter(//pages about LoD)
              .map(Page::text)
              .collect(joining(","));

      //do whatever with the content
  }
}

The library stores books in some file storage and keeps the paths to them in a map

public class Library {

  private final Map<String, Path> booksOnDisc;

  Book book(String title) {
      return new BookFromFile(booksOnDisc.get(title));
  }
}

Each book consists of a set of pages

public class BookFromFile {

  private final Path path;

  public BookFromFile(Path path) {
      this.path = path;
  }

  public Set<Page> pages() {
      //read contents of the file and create the list of pages in the book

      return pages;
  }
}
public class Page {

  private final char[] content;

  public Page(char[] content) {
      this.content = content;
  }

  String text() {
      return contentAsAString;
  }
}

In the reader class we are ‘reaching through’ the library to get a book and operating directly on it. We are not however tying ourselves to a field of Library, we are only using its exposed behaviour, the same goes with the book. The law was not broken and we didn’t have to cram all of that functionality into Library. To break the law we’d have to for example put a getter for content of a page or path to a book and operate on that value:

public class Page {

  private final char[] content;

  public Page(char[] content) {
      this.content = content;
  }

  char[] getContent() {
      return content;
  }
}
public class Reader {

  private final Library library;

  void readAboutOop() {
      String content = library.book("OOP").pages().stream()
              .filter(//pages about LoD)
              .map(Page::getContent)
              .collect(joining(","));

      //do whatever with the content
  }
}

(this won't compile but let's keep it for the sake of argument) Here we are binding the Reader class to the internals (fields) of Page - exactly what the LoD is against. An extreme example of that approach is (sadly) not that uncommon:

class Container {  
    List<Long> longs;
    //getters and setters
}

class Operator {  
    private Container container;

    method() {
        container.getLongs().add(3L);
        //more logic
    }
}

One of the arguments against chaining the operations is difficulty in testing but it's actually not that bad:

@RunWith(MockitoJUnitRunner.class)
class ReaderTest {

@Mock
Book book;  
@Mock
Library library;

@InjectMocks
Reader reader;

@Test
public void shouldReadBook() {  
  List<Page> pages = ImmutableList.of(new Page(someContent))
  given(library.borrow("OOP")).willReturn(book);
  given(book.pages()).willReturn(pages);

  //whatever we want to test
}
}

Of course the more dots we have the more stubs are required. But it shouldn’t be too terrible - all but one (last) stub simply return another stub. And it is not a problem at all when every class implements an iterface and we have 'dummy' implementations shipped with them (as described in Elegant Objects vol.1).

Was I the only one that misunderstood that law? Let me know in the comments!

P.S There's also a good article on DZone - https://dzone.com/articles/the-genius-of-the-law-of-demeter

comments powered by Disqus