Applying the Law of Demeter in Object-Oriented Programming with Rails

In object-oriented programming (OOP), adhering to principles that promote loose coupling and encapsulation is essential for building maintainable, scalable systems. One such principle is the Law of Demeter, also known as the Principle of Least Knowledge. This principle helps prevent objects from knowing too much about the internal workings of other objects, which is a common issue in OOP.

In this blog post, we’ll explore the Law of Demeter and demonstrate how you can apply it in a Ruby on Rails application. By the end, you’ll understand how this principle can simplify your code and make your application easier to maintain.

What is the Law of Demeter in Object-Oriented Programming?

In object-oriented programming, the Law of Demeter (LoD) can be summarized with the following rule: “An object should only talk to its immediate friends and not strangers.”

To put this more clearly, an object should only invoke methods on:

This rule reduces the dependencies between objects, making the code less fragile and easier to maintain. When an object reaches through another object to access a third object’s method (e.g., order.customer.address.city), it creates tight coupling. This kind of code is harder to test, extend, and debug.

The Problem: Breaking the Law of Demeter

Let’s look at a common example in a Rails application where the Law of Demeter is violated. Suppose we have three models: Order, Customer, and Address. An Order belongs to a Customer, and a Customer has an Address. In the code below, the Order is calculating a discounted shipping price based on the customer’s address.

1
2
3
4
5
6
class Order
  def discounted_shipping_price(discount_code)
    coupon = Coupon.new(discount_code)
    coupon.discount(customer.address.shipping_price)
  end
end

This implementation violates the Law of Demeter. The Order object is reaching through Customer to access Address and then fetching the shipping_price. This leads to a situation where Order knows too much about the internal structure of Customer and Address, resulting in tight coupling between these objects.

Refactoring: Applying the Law of Demeter

To fix this, we can move the responsibility of calculating the discounted shipping price to the Customer model. This way, the Order only interacts with its direct collaborator, Customer, and the Customer manages its relationship with the Address.

Here’s the refactored code:

1
2
3
4
5
6
7
8
9
10
11
12
class Order
  def discounted_shipping_price(discount_code)
    customer.discounted_shipping_price(discount_code)
  end
end

class Customer
  def discounted_shipping_price(discount_code)
    coupon = Coupon.new(discount_code)
    coupon.discount(address.shipping_price)
  end
end

Now, the Order class no longer directly accesses the Address. Instead, it delegates the calculation to the Customer class. This reduces coupling and makes the system more maintainable.

Using delegate for Even Cleaner Code

We can take this refactoring a step further by using Rails’ built-in delegate method. The delegate method allows us to automatically forward method calls from one object to another, making our code cleaner and easier to read.

Here’s how we can refactor the previous example using delegate:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Order
  belongs_to :customer

  # Delegate discounted_shipping_price to customer
  delegate :discounted_shipping_price, to: :customer
end

class Customer
  has_one :address

  def discounted_shipping_price(discount_code)
    coupon = Coupon.new(discount_code)
    coupon.discount(address.shipping_price)
  end
end

How Does This Work?

This refactor makes the code cleaner, while still respecting the Law of Demeter.

Why Follow the Law of Demeter?

1. Loose Coupling

By following the Law of Demeter, objects don’t need to know the internal details of other objects. This reduces the likelihood that a change in one part of your system will break another part. For example, if we change how Address is structured, Order won’t need to change.

2. Encapsulation

Each class is responsible for its own logic. In our example, the Customer class is responsible for managing the address and shipping price, while Order focuses on the order details. This clear separation of concerns makes the code easier to understand and maintain.

3. Better Maintainability

Less knowledge about other objects’ structures means fewer dependencies. This makes the code more flexible and easier to refactor in the future. If the business logic around shipping changes, you only need to update the Customer model without impacting Order.

The Law of Demeter is a key principle in object-oriented programming that helps reduce coupling and promote better code organization. In Rails applications, adhering to this principle results in more maintainable and scalable systems. By refactoring your code to delegate responsibilities and limit an object’s knowledge of others, you create a cleaner and more robust application architecture.

In our example with Order, Customer, and Address, we’ve seen how easy it is to break the Law of Demeter and how using Rails’ delegate method can help fix it. As your Rails application grows, applying the Law of Demeter will ensure your codebase remains clean, maintainable, and easy to extend.

Key Takeaways: