DEV Community

Caleb Hearth
Caleb Hearth

Posted on • Originally published at calebhearth.com on

The Law of Demeter

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

Enter fullscreen mode Exit fullscreen mode

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:

  1. The argument classes of M (including C)
  2. 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

  1. Makes refactoring easier when either collaborators or your system changes
  2. Makes writing code easier: you limit how much you need to think about to your immediate collaborators’ APIs
  3. 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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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)