It is usually well understood that decoupled software helps us easily change, test, reuse, understand, and extend our code. For this reason, many of us will either advocate or hear someone promoting decoupled code when we build our systems.
There are a number of techniques that we can use to do this, and today I’ll write about one in particular: the Law of Demeter.
The Law Of Demeter
I first stumbled upon the Law of Demeter while reading the Clean Code book by Robert C. Martin (a must read in my opinion). It was discovered in 1987 by Ian Holland while working on a project called Demeter, thus the name of the Law.
The Law of Demeter says that class methods should only access variables they directly know about. Or, as the motto says: “Only talk to your friends“. Despite the simplicity, I find that the words get in the way, so instead, let me start by showing some code that breaks this law.
Let’s consider some classes House
, Room
and SmartLight
:
House
implements the illuminate_rooms
method to turn on the lights in every room. When called, it finds the light object on a specific room, and calls the turn_on
method.
class House
...
def illuminate_rooms
@kitchen.light.turn_on
end
end
Although this works, this implementation of the illuminate_rooms
method breaks the Law of Demeter because class House
is sending a message (turn_on
) on an object (light
) that it’s not directly associated with.
We can easily fix this by adding a method in class Room
to hide its implementation details, and call it from class House
:
class Room
...
def illuminate
@light.turn_on
end
end
class House
...
def illuminate_rooms
@kitchen.illuminate
end
end
Now, class House
emits a message to kitchen
, one of its member variables, and so does class Room
when it calls @light.turn_on
. The Law of Demeter is then satisfied, as we are only making function calls on direct dependencies of the classes.
Formal Definition
The Law of Demeter is formally defined in a paper published in 1989 [1]. It provides two definitions of the rule: the official, which is a bit cryptic and you can find in the reference, and another that even the authors believe is “the most natural way to state the Law”, that goes like this:
For all classes C, and for all methods M attached to C, all objects to which M sends a message must be:
- M’s argument objects, including the self object, or
- The instance variable objects of C.
(Objects created by M, or by functions or methods which M calls, and objects in global variables are considered as arguments of M.)
I will complement this with some examples of function calls that adhere to the Law of Demeter:
M’s arguments
If a method M receives an object as argument, the Law of Demeter allows calling methods on this object.
class Class_C
...
def method_M(argument_object)
argument_object.message()
end
end
M’s self argument
In some languages, like Python, methods explicitly include a self
argument that corresponds to the method’s class. Other programming languages do this implicitly. Irrespective of how this is done, what this rule says is that methods in a class can call methods on the same class.
class ClassC
...
def method_M
another_method
end
def another_method
...
end
end
Objects created by M
As we saw in a previous article, we should usually prefer to follow the Dependency Inversion principle and inject dependencies, instead of creating objects inside other classes.
However, if we still decide to create objects inside other classes, the Law of Demeter treats them as if they were function arguments, and so sending messages to these objects is allowed.
class ClassC
...
def method_M
created_object = AnotherClass.new
created_object.message()
end
end
Objects created by functions or methods which M calls
This is very similar to the previous case, only that classes are not created directly by method M, but by a method call that also follows the Law of Demeter:
class ClassC
...
def method_M(argument_object)
created_object = argument_object.message()
created_object.message()
end
end
objects in global variables
Although global variables are usually a coding smell and should be avoided, the Law of Demeter has nothing against them, and making function calls to objects in global variables is permitted:
$global_variable_object = SomeClass.new
class ClassC
...
def method_M
$global_variable_object.message()
end
end
The instance variable objects of C.
Finally, the Law of Demeter establishes that a method M of class C can make function calls on any member variables of class C:
class ClassC
def initialize(some_object)
@some_instance_variable = some_object
@another_instance_variable = AnotherClass.new
end
...
def method_M
@some_instance_variable.message()
@another_instance_variable.message()
end
end
Benefits
The Law of Demeter has direct consequences for everyday developers. According to the authors, it “simplifies complexity of programming [as] it restricts the number of types the programmer has to be aware of when writing a method.”
Even more, it “has many implications regarding widely known software engineering principles“, which I’ve taken the liberty to quote and summarise here:
“Coupling controls: … The Law reduces the methods we can call inside a given method and therefore limits the coupling of methods.“
“Information hiding”: The Law enforces information hiding by preventing methods from directly accessing an object deep in the hierarchy and promoting the use of intermediary methods instead.
“Information restriction”: The Law of Demeter restricts the use of function calls, complementing information hiding.
“Localization of information: … When we study a method we only have to be aware of types which are very closely related… We can effectively be ignorant (and independent) of the rest of the system.“
“Narrow interfaces: … A method should have access to only as much information as it needs…” When a method handles too much information, it will usually end up making nested function calls, “which the Law of Demeter discourages”.
Applying The Law
The Law of Demeter presents a very simple and clear strategy to write decoupled systems. There are some tools available to enforce it, like Reek for Ruby. For other languages like Typescript, it is possible to create custom ESLint rules to catch any violations.
Even if we don’t have a tool to automatically enforce it, we can still keep the Law in mind while writing or reviewing code. This will help us build cleaner and more decoupled systems.
To get the most out of it, the authors also recommend reducing code duplication, minimising the number of arguments per method and methods per class.
Cheers!
José Miguel
Share if you find this content useful.
Follow me on LinkedIn to be notified of new articles.
References
[1] Object Oriented Programming: An Objective Sense of Style: K. Lieberherr, I. Holland, A. Riel