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.