The Law of Demeter, simply stated, says that an object should not “reach through” its collaborators to access their collaborators’ data, methods, or collaborators.
The name of the law references the name of the project the authors were coding when they coined it. The authors named the project “Demeter”, which itself is a reference to a hardware description language called “Zeus”. Demeter, the goddess of harvest and agriculture, is a sister of Zeus.
Perhaps the easiest way of describing how to obey the Law of Demeter is to give an example of disobeying it. Here, our Ranger accesses dwarf.strength.modifier
and dwarf.proficiencies.include?
, both of which are violations of the Law:
class Ranger
def toss_dwarf(dwarf, location)
dwarf_check = rand(20) + dwarf.strength.modifier + (dwarf.proficiencies.include?(:athletics) ? dwarf.proficiency_bonus : 0)
ranger_check = strength.modifier + (proficiencies.include?(:athletics) ? proficiency_bonus : 0)
if ranger_check > dwarf_check
dwarf.location = location
end
end
end
The problem here is that Ranger has more knowledge of how Dwarves work than it would need to when using other approaches. This leads to the objects being tightly coupled, which may make refactoring (“simplifies modification”), testing, and debugging (“simplifies complexity of programming”) more difficult.
Law of Demeter is also known as the Principle of Least Knowledge. The concept was first published as a paper presented by Karl J. Lieberherr, Ian Holland, and Arthur J. Riel:
For all classes C, and for all methods M attached to C, all objects to which M sends a message must be instances of classes associated with the following classes:
- The argument classes of M (including C)
- The instance variable classes 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.)
Less formally and specific to object-oriented languages that use this syntax, only use one dot in methods.
dwarf.strength.modifier
is not okay as we reach through dwarf to strength
to get modifier
.
dwarf.strength
and dwarf.strength_modifier
are no problem, as we only reach through dwarf to get strength
and strength_modifier
.
Benefits of Law of Demeter
- Makes refactoring easier when either collaborators or your system changes
- Makes writing code easier: you limit how much you need to think about to your immediate collaborators’ APIs
- Makes testing easier: mocking for isolation and constructing objects for integration is simpler when you only need to construct immediate collaborators
Don’t break the law
There are good ways in Ruby to avoid this code smell.
Delegate to prefixed methods
Rails has a shortcut to delegate methods to collaborators, so Dwarf can do this to avoid a Demeter violation in Ranger:
class Dwarf
delegate :modifier, to: :strength, prefix: true, allow_nil: true
end
Exposing strength_modifier
on Dwarf’s API allows for refactoring that method to use a different internal implementation while retaining behavior. It also simplifies mocking the method for isolated testing.
Tell, Don’t Ask
Rather than Ranger asking Dwarf about its attributes and skills to make a check, teach Dwarf to make the ability check itself and call it as dwarf.make_athletics_check
.
class Dwarf
def make_athletics_check
athletics_bonus = if proficiencies.include?(:athletics)
strength.modifier + proficiency_bonus
else
strength.modifier
end
rand(20) + athletics_bonus
end
end
Pass strength as an argument
You can technically satisfy this by passing strength
into the method as it would then become acceptable to call strength.modifier
. This is because strength
would have become an argument to the method and would be acceptable to call methods on. Doing that probably wouldn’t fly in your pull request review in the above example because it contorts itself so much to follow the Law. If there were some constraint that prevented delegation or Tell, Don’t Ask, passing strength in could be an option. In this case, I would recommend that as a time to bend the Law instead of passing in modifier
.
Exceptions to the rule
Law of Demeter is about reaching through objects to their collaborators. As such there are some exceptions when we consider interacting with other design patterns.
Builder pattern
The builder pattern, for example, expects significant method chaining and this type of chaining is not considered a violation of Demeter. An example with ActiveRecord’s Query Interface, which uses the builder pattern to build up SQL queries:
Dwarf
.where(friend: ranger)
.where(weapon: :axe)
.first # object created by our method
.name # one allowed method call
.split(",") # .split and .first are Demeter violations
.first
All of that through .first
is using ActiveRecord’s query builder, so you can think of that all as creating a new object for purposes of this rule. The .name
is our “one dot”, so .split.first
is breaking the rule but up to that point nothing is wrong with the method chaining as far as Demeter is concerned.
In fact, as Law of Demeter is stated in terms of types, because Dwarf.where
returns an ActiveRecord::Relation
object, that becomes a type that we create ourselves. Any method returning an object of type ActiveRecord::Relation
can be chained from, so the Builder pattern which returns modified objects of the same type is explicitly allowed by the Law of Demeter.
In practical terms, because we control the Dwarf
object, we can define methods such as name_parts
on it. In the method chain above, .first
returns a Dwarf
, so that is where we consider the Law of Demeter to begin to be enforced.
Map/reduce
Another acceptable example would be [].map {}.reduce {}
, a well-established pattern for manipulating data. The results of map
and reduce
fall into the “object you create” exception to the rule as each method returns a new object created by our method.
Interacting with 3rd-party data or libraries
When consuming an API, you might find yourself needing to access deeply-nested data that your application isn’t creating on its own. For example to consume the ArkhamDB API for a popular card game, you might need to dig deeply to know what cards you can add to the deck with code like:
card_json["deck_options"].each do |opt|
puts "#{opt["faction"]} (#{opt["level"]["min"]}–#{opt["level"]["max"]})"
end
In Ruby and other weakly-typed, object-oriented languages this type of chaining (remember that .[]
is a method) is an acceptable exception to Law of Demeter. In strongly typed languages such as Go or Swift, you would probably create structured data objects to decode this JSON into and in that case you could define methods such as card.factionOptions()
to remain within the Law.
You don’t always control the API surface a gem or library provides you. Sometimes you’ll need to break the Law of Demeter to interact with code that you don’t own, and that’s acceptable as well. A piece of advice I’ll offer here is that you may want to encapsulate third-party libraries and APIs into Gateways that both provide a seam in your own code for faking and can encapsulate code that necessarily looks and acts differently than the rest of your system.
Conform to the Law of Demeter, or don’t
The Law of Demeter points out potential issues in your code. While strict adherence to the rule is an option, practicality and style might push you toward some violations of the Law. It’s okay to have a small number of such violations in a codebase, and you should feel free to break this “law” as you would any other best practice when it makes sense for your code. As Martin Fowler puts it, a better name might be the “Occasionally Useful Suggestion of Demeter”.
If you do see method chaining, it should stand out as something to be second guessed and you should consider avoiding or refactoring that code, but feel free to consider and decide against it.
Top comments (0)