Sonntag, 10. August 2014

Do not break the law of Demeter!

The definition of the law of Demeter also known as the principle of least knowledge is:
A given object should assume as little as possible about the structure or properties of anything else (including its subcomponents).
It also can be described by:
  1. Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
  2. Each unit should only talk to its friends; don't talk to strangers.
  3. Only talk to your immediate friends.
Given the following example with 3 classes (ActiveRecord models):
class Category < ActiveRecord::Base
  attr_accessible :name
end

class Food < ActiveRecord::Base
  attr_accessible :name, :category_id
  belongs_to :category
end

class Recipe < ActiveRecord::Base
  attr_accessible :name, :food_id
  belongs_to :food
end
and accessing the recipes category name in the ERB template in that way:
Recipe: <%= @recipe.name %>
Recipe food category: <%= @recipe.food.category.name if @recipe.food and @recipe.food.category %>
is definitely a bad practice for various reasons:
  1. The Recipe object has not only to know about its associated Food object, but also about how the food object is associated to the Category object.
  2. The path gets longer the more objects are involved and the more the Recipe object has to know about other objects associations.
  3. An object during the path could be nil, and hence the path could be broken. Therefore a long security condition statement is needed every time the path is gone.
  4. Repeating the security condition everywhere the path was used, is hard to maintain, if even only one association changed.
Those points mean that the law of Demeter was broken.
It can be fixed by hiding the knowledge about the directly associated object in an instance method like:
class Category < ActiveRecord::Base
  attr_accessible :name
end

class Food < ActiveRecord::Base
  attr_accessible :name, :category_id
  belongs_to :category

  def category_name
    category.name  if category
  end
end

class Recipe < ActiveRecord::Base
  attr_accessible :name, :food_id
  belongs_to :food

  def food_category_name
    food.category_name if food
  end
end
The Recipe object only knows about its associated Food object and how to get its category_name, without knowing HOW the Food object gets the category_name. But the Food object just knows how to get the Category name. The knowledge was appropriately hidden and can accessed like:
Recipe: <%= @recipe.name %>
Recipe food category: <%= @recipe.food_category_name %>
Well, Ruby on Rails is handy enough to offer some syntactic sugar for delegating methods with the help of Module#delegate. That is why the models look better delegating the Category name:
class Category < ActiveRecord::Base
  attr_accessible :name
end

class Food < ActiveRecord::Base
  attr_accessible :name, :category_id
  belongs_to :category

  delegate :name, to: :category, prefix: true, allow_nil: true
end

class Recipe < ActiveRecord::Base
  attr_accessible :name, :food_id
  belongs_to :food

  delegate :category_name, to: :food, prefix: true, allow_nil: true
end
The detailed options can be read in the Module#delegate API documentation. The ERB template need not to be changed:
Recipe: <%= @recipe.name %>
Recipe food category: <%= @recipe.food_category_name %>

Supported by Ruby 2.1.1 and Ruby on Rails 3.2.17

Keine Kommentare:

Kommentar veröffentlichen