15

I came back to work from vacation yesterday, and in our daily standup, my teammates mentioned they were refactoring all of the model objects in our java code to remove all getters and setters and make the model fields all public objects instead, invoking the Law of Demeter as the reason for doing so because

to facilitate the our adherence to Demeter's law: a module should not know about the innards of the 'objects' it manipulates. Since data structures contain no behavior, they naturally exposes their internal structure. So in that case, Demeter does not apply.

I admit I had to brush up on my knowledge of the LoD, but for the life of me I can't find anything to indicate that this is within the spirit of the law. None of the getters/setters in our models contain any business logic, which is his justification for doing this so clients of these objects don't have to understand whether or not there is some business logic being executed within the get/set methods.

I think this is a misunderstanding of what it means to need 'internal knowledge of an objects structure', or at the very least is taking it too literally and breaking a pretty standard convention in the process.

So my question is, does it actually make any sense to expose model objects internal structure directly instead of via getters/setters in the name of the LoD?

Lii
  • 11,553
  • 8
  • 64
  • 88
alexD
  • 2,368
  • 2
  • 32
  • 43
  • 7
    It sounds like your teammates don't have enough work to do. – Sneftel Sep 24 '14 at 15:45
  • 1
    Yeah, that's the problem..we have a ton of work to do, and this is a huge refactor and touches like 100 files. I'm going to push back, and I just want to make sure I have more than just "it's a waste of time" (which should be enough on it's own though) – alexD Sep 24 '14 at 15:51
  • Luckily you are not using Groovy where the `x.getStuff()` and `x.stuff` are indistinguishable (and where the field `stuff` may not even exist at call time...) – David Tonhofer Feb 21 '16 at 00:13
  • @alexD: From where is the quote in the question taken? – Lii Apr 20 '17 at 08:38

3 Answers3

20

There is a book called Clean Code by Robert Martin that covers this.

In Chapter 6 (Objects and Data Structures) he talks about fundamental differences between objects and data structures. Objects benefit from encapsulation, data structures don't.

There is a section about the Law of Demeter:

There is a well-known heuristic called the Law of Demeter that says a module should not know about the innards of the objects it manipulates. As we saw in the last section, objects hide their data and expose operations. This means that an object should not expose its internal structure through accessors because to do so is to expose, rather than to hide, its internal structure.

More precisely, the Law of Demeter says that a method f of a class C should only call the methods of these:

  • C
  • An object created by f
  • An object passed as an argument to f
  • An object held in an instance variable of C

The method should not invoke methods on objects that are returned by any of the allowed functions. In other words, talk to friends, not to strangers.

Uncle Bob gives an example of a LoD violation:

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.

The use of accessor functions confuses the issue. If the code had been written as follows, then we probably wouldn’t be asking about Demeter violations.

final String outputDir = ctxt.options.scratchDir.absolutePath;

So this is probably where your co-workers are coming from. I think the argument "we have to do this because LoD" is imprecise at best. The central issue isn't LoD so much as whether the API consists of objects or data structures. It does seem like an unnecessary and error-prone change to push through when there are more pressing things to do.

Szabolcs Antal
  • 877
  • 4
  • 15
  • 27
Nathan Hughes
  • 94,330
  • 19
  • 181
  • 276
  • 2
    Makes sense..there is a copy of "Clean Code" sitting on his desk. Guess he was reading it recently :) – alexD Sep 24 '14 at 16:22
  • I disagree. I think it's still a problem. Think of it this way - if you need to test the class using outputDir, do you really want to instantiate entire object tree -- ctxt, options and scratchDir? A great talk about this - https://www.youtube.com/watch?v=RlfLCWKxHJ0 – alexandroid Jun 19 '15 at 17:45
  • Robert Martin's distinction between "objects" and "data structures" does not sound pertinent. "Objects" are "data structures", "data structures" are "Objects". Code refactoring as well as "looking at the code from another perspective" will morph one into the other. Maybe if "data structures" are understood to be the "container" types like Map, List, Set etc. – David Tonhofer Feb 21 '16 at 00:08
  • @David: i think what Martin is saying is that sometimes encapsulation is helpful and sometimes it isn't, and when it isn't helpful then he tries not to call it an object. At the time Martin wrote that book he was dipping his toes in the FP paradigm and he seemed to be getting more of an appreciation for OOP being relevant more in some places than in others, which I didn't notice so much in earlier writing. – Nathan Hughes Feb 21 '16 at 01:43
13

It doesn't seem to me that this change has anything to do with the Law of Demeter. The law is, essentially, about encoding the structure of your object graph into your code by having methods call through an entire chain of other objects. For example, suppose, in a car insurance application, that a customer has a policy, and a policy has vehicles, and vehicles have drivers assigned to them, and drivers have birth dates and, thus ages. You could imagine the following code:

public boolean hasUnderageDrivers(Customer customer) {
    for (Vehicle vehicle : customer.getPolicy().getVehicles()) {
        for (Driver driver : vehicle.getDrivers()) {
            if (driver.getAge() < 18) {
                return true;
            }
        }
    }
    return false;
}

This would violate the Law of Demeter because this code now has knowledge of internals that it doesn't need to know. It knows that drivers are assigned to vehicles, instead of just being assigned to the insurance policy as a whole. If, in the future, the insurance company decided that drivers would simply be on the policy, instead of being assigned to particular vehicles, then this code would have to change.

The problem is that it calls a method of its parameter, getPolicy(), and then another, getVehicles(), and then another, getDrivers(), and then another, getAge(). The Law of Demeter says that a method of a class should only call methods on:

  • Itself
  • Its fields
  • Its parameters
  • Objects it creates

(The last one can be a problem for unit testing, where you may want to have objects injected or created by factories rather than created directly locally, but that's not relevant to the Law of Demeter.)

To fix the problem with hasUnderageDrivers() we can pass in the Policy object and we can have a method on Policy that knows how to determine whether the policy has underage drivers:

public boolean hasUnderageDrivers(Policy policy) {
    return policy.hasUnderageDrivers();
}

Calling one level down, customer.getPolicy().hasUnderageDrivers(), is probably okay — the Law of Demeter is a rule of thumb, not a hard-and-fast rule. You also probably don't have to worry much about things that aren't likely to change; Driver is probably always going to continue to have a birth date and a getAge() method.

But returning to your case, what happens if we replace all these getters with public fields? It doesn't help with the Law of Demeter at all. You can still have the exact same problem as in the first example. Consider:

public boolean hasUnderageDrivers(Customer customer) {
    for (Vehicle vehicle : customer.policy.vehicles) {
        for (Driver driver : vehicle.drivers) {
            if (driver.age < 18) {
                return true;
            }
        }
    }
    return false;
}

(I've even converted driver.getAge() to driver.age, although that would probably be a calculation based on the birth date rather than a simple field.)

Notice that the exact same problem with embedding knowledge of how the object graph is put together (a customer has a policy which has vehicles which have drivers) is present when we write the code with public fields instead of getters. The problem has to do with how the pieces are put together, not with whether getters are being called.

Incidentally, the normal reason to prefers getters over (final?) public fields is that you may need to put some logic behind them later on. An age is replaced with a calculation based on the birth date and today's date, or a setter needs to have some validation (throws if you pass null, for instance) associated with it. I haven't heard the Law of Demeter invoked in that context before.

David Conrad
  • 15,432
  • 2
  • 42
  • 54
  • 2
    I have a question regarding your (excellent) policy example. Wouldn't you end up with a lot of code in your policy object. As everyone wanting to know anything about insurance contracts would have to ask the policy object first? – markus Feb 20 '15 at 08:10
  • 2
    @markus It's true, you may end up with a lot of code in the policy class. You may also end up with a lot of little "forwarding" methods, if the `hasUnderageDrivers` method in `Policy` just passes the responsibility on to a similarly-named method on some `Vehicles` collection object. – David Conrad Mar 02 '15 at 16:32
  • 1
    @markus Wikipedia [mention this](https://en.wikipedia.org/wiki/Law_of_Demeter#Disadvantages): _"At the method level, the LoD leads to narrow interfaces, giving access to only as much information as it needs to do its job, as each method needs to know about a small set of methods of closely related objects. OTOH, at the class level, the LoD leads to wide (i.e. enlarged) interfaces, because the LoD requires introducing many auxiliary methods instead of digging directly into the object structures. One solution to the problem of enlarged class interfaces is the aspect-oriented approach..."_ – David Tonhofer Feb 20 '16 at 23:57
0

The Law of Demeter about the working with objects, not data structure, in your case DTO as I understand.

The Law of Demeter explains that you can call methods of objects that are:

  1. Passed as arguments
  2. Cleated locally inside the method
  3. Instance variables (object's fields)
  4. Global

Data models represent containers with some data inside them that should be shown outside. It's their role and they haven't some other behavior besides this.

Dmytro Melnychuk
  • 2,285
  • 21
  • 23