Law of Demeter
The Law of Demeter was developed at Northeastern University. It’s named after the Demeter Project, which is itself named after Demeter, the Greek goddess of the harvest. There is widespread disagreement as to its pronunciation, but the correct pronunciation emphasizes the second syllable; you can trust us on that.
This principle states that:
A method of an object should invoke only the methods of the following kinds of objects:
- itself
- its parameters
- any objects it creates/instantiates
- its direct component objects
Like many principles, the Law of Demeter is an attempt to help developers manage dependencies. The law restricts how deeply a method can reach into another object’s dependency graph, preventing any one method from becoming tightly coupled to another object’s structure.
Multiple Dots
The most obvious violation of the Law of Demeter is “multiple dots”, meaning a chain of methods being invoked on each others’ return values.
Eg:
class User
def discounted_plan_price(discount_code)
coupon = Coupon.new(discount_code)
coupon.discount(account.plan.price)
end
end
account.plan.price
violates the Law of Demeter. The price method is not a method on User, its parameter discount_code
, its instantiated object coupon
, or its direct component account
.
Refactored:
class User
def discounted_plan_price(discount_code)
account.discounted_plan_price(discount_code)
end
end
class Account
def discounted_plan_price(discount_code)
coupon = Coupon.new(discount_code)
coupon.discount(plan.price)
end
end
in Rails, we can delegate
class User
delegate :discounted_plan_price, to: :account
end
Multiple Assignments
Law of Demeter violations are often hidden behind multiple assignments.
class User
def discounted_plan_price(discount_code)
coupon = Coupon.new(discount_code)
plan = account.plan
coupon.discount(plan.price)
end
end
The Spirit of the Law
TODO
Objects vs Types
TODO
Duplication
The Law of Demeter is related to the DRY principle, in that Law of Demeter violations frequently duplicate knowledge of dependencies.
class CreditCardsController < ApplicationController
def charge_for_plan
if current_user.account.credit_card.valid?
price = current_user.account.plan.price
current_user.account.credit_card.charge price
end
end
end
In this example, the knowledge that a user has a credit card through its account is duplicated. That knowledge is declared somewhere in the User
and Account
classes when the relationship is defined, and then knowledge of it spreads to two more locations in charge_for_plan
.
Like most duplication, each instance isn’t too harmful, but in aggregate, dupli- cation will make refactoring slowly become impossible.