The law of Demeter
I read multiple things about the Law of Demeter but I never truly understood what it really means until I read Sandi Metz’s book Practical Object-Oriented Design in Ruby (POODR). This article is shamelessly inspired from her work :smile:
Definition
It’s quite hard to understand the Law of Demeter when encountered for the first time. As seen in the definition from Wikipedia :
More formally, the Law of Demeter for functions requires that a
methodof anobjectmay only invoke the methods of the following kinds of objects :
objectitselfmethod’s parameters- Any objects created/instantiated within
methodobject’s direct component objects- A global variable, accessible by
object, in the scope ofmethod
More generally, Law of Demeter is a range of coding rules that helps keep our objects loosely coupled. We always refer it as the “use only one dot” rule or “only talk to your immediate neighbors”.
But what does it really mean ?
What Demeter is telling us
Consider we have a Rental class that contains a depart method. And we have an Owner model, that own a Car. A Car has an Engine which contains an oil_level attribute and a start method. Let’s say we have implemented our depart method like this :
class Rental
def depart
if owner.car.engine.oil_level > 10
owner.car.engine.start
else
puts "Oil level too low"
end
end
endThe chaining dots in lines 4 and 5 are almost identical. The first one retrieves a distant attribute (oil_level) and the second one invokes a distant behavior (start).
What’s wrong with those lines ?
It’s a clear Demeter violation. Let’s list our issues with this piece of code :
- If we change something in
Engine, it might break something inRental, which is completely unrelated. This code is not transparent. Rentalcannot be reused unless it has access to anownerthat has acarwhich contains anenginethat responds tostart.
This design increases the coupling of our application, and coupling is bad, really bad. You know that moment when you change something in class A and suddenly, it breaks another thing in class B ? Well, most of the time it’s due to the fact that your application is too much coupled.
Now think about it. In our example, Rental knows that an owner has a car with an engine and it also knows that this engine can be started. It knows way too much.
So, what can we do about that ?
Object-Oriented Design is about managing dependencies. — Sandi Metz
Note : in owner.car.engine.oil_level we try to access a distant attribute. It is also a Demeter violation but it is a particular case because it might be cheaper not to change this. In some cases, if we just need to access the attribute, we can keep this as it is. Just remember that this tradeoff is permitted as long as we are not changing the value of the attribute we retrieve. If we change the value of the attribute, we are implementing a new behavior that belongs to Engine, not Rental`.
Focus on behavior rather than data
What if we can do something like that :
class Rental
def depart
owner.drive
end
endWouldn’t be amazing ?
Well, guess what ? We can.
And by doing so, we are trusting other objects to behave according to the message we are sending them. As Rental, we don’t want to know how the owner plans to drive and start his car. We just need to trust him that he will.
Then, we can implement the drive method in our Owner class and delegate the start part to the last object that needs to know about it : Engine. We end up with this :
class Rental
def depart
owner.drive
end
end
class Owner
def drive
car.switch_on
end
end
class Car
def switch_on
engine.start
end
end
class Engine
def start
if oil_level > 10
# start the engine
else
puts "Oil level too low"
end
end
endThose small modifications increase significatively the quality of our code. Each object trusts each other that it will do its job.
Rental is now “open for extension and closed for modification”.
What does it mean exactly ? I think it deserves a post on its own.