Mastering the Law of Demeter: A Tester’s Guide to Code Excellence

Daniel Delimata
11 min readSep 24, 2023

The Law of Demeter (LoD) is a design guideline for developing software, particularly object-oriented programs. It is also known as the principle of least knowledge or the rule of limiting interactions. It states that an object should only communicate with its direct friends and not with strangers. But what does this mean for testers? How can we apply this principle to improve the quality and maintainability of our code? In this article, we will explore the benefits and challenges of following the Law of Demeter, and how to resolve its violations in Java.

Demeter
Demeter

WHAT IS THE LAW OF DEMETER?

The Law of Demeter was proposed by Ian Holland in 1987 at Northeastern University as part of the Demeter Project, an adaptive programming and aspect-oriented programming effort. The project was named after Demeter, the Greek goddess of agriculture, to signify a bottom-up philosophy of programming that is also embodied in the law itself.

The Law of Demeter for functions requires that a method m of an object a may only invoke the methods of the following kinds of objects:

  • The object itself (a)
  • Any parameter passed to m
  • Any object created by m
  • Any direct component of a

In other words, an object should only talk to its immediate friends and not reach through them to access other objects. This way, the object assumes as little as possible about the structure or properties of anything else, in accordance with the principle of information hiding.

For example, consider the following code snippet:

public class Customer {
private Wallet wallet;

public Wallet getWallet() {
return wallet;
}
}

public class Wallet {
private int money;

public int getMoney() {
return money;
}
}

public class Paperboy {
public void collectMoney(Customer customer) {
int payment = customer.getWallet().getMoney();
// do something with payment
}
}

In this example, the paperboy object violates the Law of Demeter by calling customer.getWallet().getMoney(). This means that the paperboy object knows too much about the internal structure of the customer object and its wallet component. If the customer object changes its implementation, such as using a credit card instead of a wallet, the paperboy object will have to change as well.

A better way to write this code would be to add a method getPayment() to the customer object that returns the amount of money that the customer can pay. This way, the paperboy object only needs to know about the customer object and not its wallet component.

public class Customer {
private Wallet wallet;

public int getPayment() {
return wallet.getMoney();
}
}

public class Paperboy {
public void collectMoney(Customer customer) {
int payment = customer.getPayment();
// do something with payment
}
}

This code follows the Law of Demeter by reducing the coupling between objects and increasing their cohesion.

We can say that this refactoring was a happy path of using LoD. Before going to more difficult cases, let us summarize the benefits.

From this section, in my opinion, the most important and worth remembering thing is that you should make your object ‘not know too much about the internal structure of other objects’. The strict wording of this rule is not so important. Why? Because if your object ‘knows too much,’ it means that you will have to know too much when maintaining it.

WHAT ARE THE BENEFITS OF THE LAW OF DEMETER?

Following the Law of Demeter has several benefits for software development and testing:

  • It improves modularity and readability by making each object responsible for a single task and delegating other tasks to its friends.
  • It reduces coupling and increases cohesion by minimizing the dependencies between objects and making them more self-contained.
  • It enhances reusability and maintainability by making each object more adaptable and easier to change without affecting other objects.
  • It facilitates testing and debugging by isolating each object’s behavior and making it easier to mock or stub its friends.

By applying the Law of Demeter, we can create software that is more robust, flexible, and testable.

Let us imagine that you want to copy some class from one project to another one. What should you copy? The class itself (of course) and… here the usefulness of LoD becomes clear. The more dependencies you have, the more difficult such an operation is.

DOES FLUENT INTERFACE IN JAVA BREAK THE LAW OF DEMETER?

You may read on some pages a short sentence ‘use only one dot`. On the other hand, method chaining is a popular, efficient, and very readable way of designing the code. Is it good or bad? Let us look at it from basics.

A fluent interface is a way of designing an API that allows chaining method calls in a readable and expressive way. For example, consider the following code snippet:

new Car().setColor("red").setModel("Tesla").setYear(2020).drive();

This code uses a fluent interface to create and drive a car object by chaining several setter methods. Each setter method returns a reference to the same car object, allowing us to call another method on it.

However, this code also seems to violate the Law of Demeter by calling multiple methods on a single object. Does this mean that fluent interfaces are bad for software design?

Not necessarily. Fluent interfaces are a special case of method chaining that can be used to improve readability and expressiveness without compromising modularity and testability. The key difference between fluent interfaces and other forms of method chaining is that fluent interfaces only call methods on the same object or its direct components, while other forms of method chaining may call methods on different objects that are not directly related.

For example, consider the following code snippet:

new Car().getEngine().start().getFuelTank().fill(50).getGauge().read();

This code uses method chaining to access and manipulate different components of a car object. However, this code violates the Law of Demeter by reaching through the car object to access its engine, fuel tank, and gauge components. This means that the code depends on the internal structure of the car object and its components, making it more fragile and harder to test.

A better way to write this code would be to encapsulate the behavior of each component within the car object and expose only the relevant methods to the outside world. For example:

new Car().startEngine().fillFuelTank(50).readGauge();

This code follows the Law of Demeter by only calling methods on the car object and not its components. This way, the code is more modular and testable, while still being readable and expressive.

Therefore, fluent interfaces do not necessarily break the Law of Demeter, as long as they only call methods on the same object or its direct components. Fluent interfaces can be a useful tool for creating clear and concise APIs that are easy to use and test.

So please remember. Using LoD is not just a counting dots exercise. It is a matter of knowledge of the internal structure.

WHY ARE GETTERS AND SETTERS IN JAVA NOT A SOLUTION TO VIOLATIONS OF THE LAW OF DEMETER?

Getters and setters are methods that allow accessing and modifying the properties of an object. For example, consider the following code snippet:

public class Person {
private String name;
private int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

This code defines a person object with two properties: name and age. It also provides getters and setters for these properties, allowing other objects to access and modify them.

However, getters and setters are not a solution for violating the Law of Demeter. In fact, they may be a sign of poor design that exposes too much information about an object’s internal state. By using getters and setters, we are essentially making the properties of an object public, allowing other objects to manipulate them directly. This breaks the principle of information hiding and increases coupling between objects.

For example, consider the following code snippet:

public class BankAccount {
private Person owner;
private double balance;

public Person getOwner() {
return owner;
}

public void setOwner(Person owner) {
this.owner = owner;
}

public double getBalance() {
return balance;
}

public void setBalance(double balance) {
this.balance = balance;
}
}

public class ATM {
public void withdrawMoney(BankAccount account, double amount) {
if (account.getOwner().getAge() >= 18 && account.getBalance() >= amount) {
account.setBalance(account.getBalance() - amount);
// dispense cash
} else {
// display an error message
}
}
}

In this example, the ATM object violates the Law of Demeter by calling account.getOwner().getAge() and account.getBalance(). This means that the ATM object knows too much about the internal structure of the bank account object and its owner component. If the bank account object changes its implementation, such as using a different type of owner or a different way of storing balance, the ATM object will have to change as well.

A better way to write this code would be to add a method canWithdraw() to the bank account object that checks if the owner is old enough and has enough balance. This way, the ATM object only needs to know about the bank account object and not its owner component.

public class BankAccount {
private Person owner;
private double balance;

public boolean canWithdraw(double amount) {
return owner.getAge() >= 18 && balance >= amount;
}

public void withdraw(double amount) {
balance -= amount;
}
}

public class ATM {
public void withdrawMoney(BankAccount account, double amount) {
if (account.canWithdraw(amount)) {
account.withdraw(amount);
// dispense cash
} else {
// display an error message
}
}
}

This code follows the Law of Demeter by reducing the coupling between objects and increasing their cohesion.

Therefore, getters and setters are not a solution for violating the Law of Demeter. Instead, they may indicate a need for refactoring and encapsulation. We should avoid exposing too much information about an object’s internal state and behavior.

WHAT IS THE RELATIONSHIP BETWEEN THE LAW OF DEMETER AND DESIGN PATTERNS?

Design patterns are reusable solutions to common problems in software design. They provide a common vocabulary and a best practice for structuring and organizing code. However, not all design patterns are compatible with the Law of Demeter. Some design patterns may encourage or require violating the Law of Demeter, while others may support or enforce it.

For example, consider the following design patterns that may violate the Law of Demeter:

  • The Chain of Responsibility pattern allows an object to pass a request along a chain of handlers until one of them handles it. This pattern may violate the Law of Demeter by making an object depend on the internal structure of the chain and its handlers.
  • The Composite pattern allows an object to treat a group of objects as a single object. This pattern may violate the Law of Demeter by making an object access the methods or properties of the group members directly.
  • The Facade pattern provides a simplified interface to a complex subsystem. This pattern may violate the Law of Demeter by making an object access the methods or properties of the subsystem components directly.

On the other hand, consider the following design patterns that may support or enforce the Law of Demeter:

  • The Adapter pattern allows an object to adapt to a different interface that is expected by another object. This pattern supports the Law of Demeter by hiding the details of the adapted object and providing only the relevant methods to the other object.
  • The Decorator pattern allows an object to add new functionality to another object without changing its structure. This pattern supports the Law of Demeter by wrapping the original object and delegating its requests to it.
  • The Proxy pattern allows an object to control access to another object by providing a surrogate or placeholder for it. This pattern enforces the Law of Demeter by intercepting and filtering the requests to the original object.

Therefore, when using design patterns, we should be aware of their impact on the Law of Demeter and choose them wisely according to our needs and goals.

HOW TO RESOLVE VIOLATIONS OF THE LAW OF DEMETER IN JAVA?

There are several ways to resolve violations of the Law of Demeter in Java, depending on the context and the severity of the violation. Some possible solutions are:

  • Refactor the code to eliminate unnecessary method chaining and replace it with direct method calls on relevant objects. In short: ‘Don’t ask! Just tell!’.
  • Introduce new methods or classes that encapsulate the behavior or data that is accessed through method chaining and provide a simpler interface for it.
  • Use dependency injection or inversion of control to inject or obtain references to relevant objects instead of accessing them through method chaining.
  • Use mocking or stubbing frameworks to isolate and test objects that depend on method chaining without affecting their collaborators.

For example, consider the following code snippet that violates the Law of Demeter:

public class Order {
private Customer customer;

public Customer getCustomer() {
return customer;
}
}

public class Customer {
private Address address;

public Address getAddress() {
return address;
}
}

public class Address {
private String street;
private String city;
private String zip;

public String getStreet() {
return street;
}

public String getCity() {
return city;
}

public String getZip() {
return zip;
}
}

public class Printer {
public void printOrder(Order order) {
System.out.println("Order details:");
System.out.println("Customer name: " + order.getCustomer().getName());
System.out.println("Customer address: " + order.getCustomer().getAddress().getStreet() + ", " + order.getCustomer().getAddress().getCity() + ", " + order.getCustomer().getAddress().getZip());
// print other details
}
}

In this example, the printer object violates the Law of Demeter by calling order.getCustomer().getAddress().getStreet() and so on. This means that the printer object knows too much about the internal structure of the bank account object and its owner component. If the bank account object changes its implementation, such as using a different type of owner or a different way of storing balance, the printer object will have to change as well.

A better way to write this code would be to add a method printDetails() to the bank account object that prints its own details and its owner's details. This way, the printer object only needs to call one method on the bank account object and not access its properties directly.

public class Order {
private Customer customer;

public void printDetails() {
System.out.println("Order details:");
System.out.println("Customer name: " + customer.getName());
System.out.println("Customer address: " + customer.getAddress());
// print other details
}
}

public class Printer {
public void printOrder(Order order) {
order.printDetails();
// print other details
}
}

This code follows the Law of Demeter by reducing the coupling between objects and increasing their cohesion.

A TRAP OF FORMAL DEFINITIONS OF LOD

Let us consider the following case to illustrate why formal definitions of LoD are sometimes useless.

We want to implement an object model of the contents of a table. Tables have rows and columns, so we implement appropriate objects for them.

The following code violates LoD.

Table table = new Table();
System.out.println(table.getRow().getCell());

In this case, we could do the following thing to ‘resolve’ the problem. We can create a new object by copying the existing one. We can create a copying constructor. Such constructors are commonly used in C++ language, but it is not a problem to create it in e.g. Java. So what will we get?

Table table = new Table();
//...
Row row = new Row(table.getRow());
Cell cell = new Cell(row.getCell());
System.out.println(cell.getI());

This code is formally okay, but the code is not simpler at all. Additionally, the problem of knowing too much about other objects still exists.

We should not focus on fulfilling the formal definition. We should think about making our object model simpler.

CONCLUSION

The Law of Demeter is a design guideline that helps us create software that is more modular, testable, and maintainable. It states that an object should only communicate with its direct friends and not with strangers. By following this principle, we can avoid unnecessary dependencies and complexity in our code.

However, the Law of Demeter is not a strict rule that must be followed in every situation. There may be cases where violating the Law of Demeter is justified or unavoidable, such as when using certain design patterns or frameworks. In such cases, we should weigh the pros and cons of following or breaking the Law of Demeter and choose the best option for our needs and goals.

The story was originally created by me, but it may contain parts that were created with AI assistance. My original text has been corrected and partially rephrased by Chat Generative Pre-trained Transformer to improve the language.

If you like this article and would be interested to read the next ones, the best way to support my work is to become a Medium member using this link:

If you are already a member and want to support this work, just follow me on Medium.

--

--