The Law of Demeter: A Guide to Better Object-Oriented Design

Origins and History

The Law of Demeter (LoD), also referred to as the Principle of Least Knowledge, was established in 1987 at Northeastern University as part of the Demeter project. This principle is named after Demeter, the Greek goddess of agriculture, symbolizing growth and the organic structure of software systems. It was first introduced by Karl Lieberherr, Ian Holland, and Arthur Riel during their work on aspect-oriented programming.

Core Concept

The Law of Demeter serves as a design guideline stating that a software unit should only interact with its immediate associates. Specifically, an object should possess limited knowledge about other objects and should only engage in communication with those to which it is closely connected. In essence, the principle can be summarized as “Do not communicate with distant objects.

The Rules

A method M of an object O should only invoke methods of:

  1. O itself
  2. Parameters of M
  3. Any objects created or instanced within M
  4. Direct component objects of O
  5. Global objects accessible to O

C# Examples

Violating the Law of Demeter

public class Customer
{
    public Wallet Wallet { get; set; }
}

public class Wallet
{
    public Money Money { get; set; }
}

public class Money
{
    public decimal Amount { get; set; }
}

// Violation of LoD
public class PaymentProcessor
{
    public void ProcessPayment(Customer customer, decimal amount)
    {
        // Navigating through multiple objects - violation
        if (customer.Wallet.Money.Amount >= amount)
        {
            customer.Wallet.Money.Amount -= amount;
        }
    }
}

Following the Law of Demeter

public class Customer
{
    private Wallet _wallet;

    public bool CanPay(decimal amount)
    {
        return _wallet.HasSufficientFunds(amount);
    }

    public void DeductAmount(decimal amount)
    {
        _wallet.DeductAmount(amount);
    }
}

public class Wallet
{
    private Money _money;

    public bool HasSufficientFunds(decimal amount)
    {
        return _money.Amount >= amount;
    }

    public void DeductAmount(decimal amount)
    {
        if (HasSufficientFunds(amount))
        {
            _money.Amount -= amount;
        }
    }
}

// Following LoD
public class PaymentProcessor
{
    public void ProcessPayment(Customer customer, decimal amount)
    {
        if (customer.CanPay(amount))
        {
            customer.DeductAmount(amount);
        }
    }
}

Common Code Smells

Chain Calls

Extended chains of method calls represent a clear violation:

customer.GetAddress().GetCountry().GetPostalCode().Format()

Train Wrecks

The presence of multiple method calls on a single line of code:

 order.GetCustomer().GetWallet().GetBalance().ToString()

Inappropriate Intimacy

Situations where a class has excessive knowledge about another class’s internal structure:

 public void ProcessOrder(Order order)
{
    // Excessive knowledge about Order's internal structure

   order.Items.ForEach(item =>       item.Inventory.Stock.Reduce(item.Quantity));

}

Middle Man Violation

Instances where intermediate objects are bypassed:

 public class Department
   {
       public Employee Manager { get; set; }

       // Violates LoD by accessing Employee properties through Manager

       public string GetManagerHomeAddress()
       {
           return $"{Manager.Address.Street},{Manager.Address.City}";
       }
   }

Benefits of Following LoD

Reduced Coupling: Systems become more maintainable as objects exhibit less dependency on the internal structures of other objects.

Increased Encapsulation: Internal details are more effectively concealed, facilitating implementation changes without impacting other parts of the system.

Better Abstraction: The principle encourages improved abstraction by prompting objects to manage their own responsibilities rather than exposing internal workings.

Improved Testability: With fewer dependencies and enhanced encapsulation, classes become simpler to test in isolation.

Common Criticisms and Trade-offs

Wrapper Methods: Strict adherence to LoD may lead to a proliferation of wrapper methods, potentially resulting in verbose code.

Performance: In some instances, following LoD might add additional method calls, although this is rarely a significant concern regarding performance in modern systems.

Complexity vs. Simplicity: Adhering to LoD might sometimes render straightforward operations more complex due to the added layers of abstraction.

Best Practices

  • Apply the Tell, Don’t Ask principle alongside LoD.
  • Develop meaningful abstractions instead of merely creating delegation methods.
  • Treat LoD as a guideline rather than an absolute rule.
  • Pay particular attention to method chains during code reviews.
  • Utilize domain-driven design principles to establish appropriate boundaries.

The Law of Demeter continues to be a significant principle in software design, advocating for loose coupling and high cohesion. While it should not be applied rigorously, a solid understanding and general adherence to its guidelines can result in more maintainable and resilient software systems. The emphasis lies in striking the right balance between strict compliance and practical implementation tailored to specific contexts and requirements.


Posted

in

by