Skip to content

Law of Demeter


Definition

Law of Demeter Definition


Violations (Train Wrecks)

// BAD: Method chain reaching deep into object graph

public class OrderService {

    void processOrder(Order order) {
        // Violation: Reaching through multiple objects
        String cityName = order
            .getCustomer()          // Not my friend
            .getAddress()           // Not my friend
            .getCity()              // Not my friend
            .getName();             // Too deep!

        // Violation: Getting internal objects and manipulating them
        Money balance = order
            .getCustomer()
            .getWallet()
            .getBalance();

        if (balance.isGreaterThan(order.getTotal())) {
            order.getCustomer().getWallet().debit(order.getTotal());
        }
    }
}

// Problems:
// - OrderService knows too much about Customer structure
// - If Wallet changes to PaymentMethods, OrderService breaks
// - If Address structure changes, OrderService breaks
// - Tightly coupled to entire object graph

Proper Implementation

// GOOD: Only talk to immediate friends

public class OrderService {

    void processOrder(Order order) {
        // Tell, don't ask - Order handles its own payment
        order.process();
    }
}

public class Order {
    private Customer customer;
    private Money total;

    void process() {
        // Order talks to its direct field (customer)
        customer.charge(total);
    }

    String getShippingCity() {
        // Delegate to customer, don't expose Address
        return customer.getShippingCity();
    }
}

public class Customer {
    private Wallet wallet;
    private Address address;

    void charge(Money amount) {
        // Customer talks to its direct field (wallet)
        wallet.debit(amount);
    }

    String getShippingCity() {
        // Customer talks to its direct field (address)
        return address.getCityName();
    }
}

// Now OrderService only knows about Order
// Customer structure can change without affecting OrderService
// Much better encapsulation!

What's Allowed

public class Example {

    private Helper helper;  // Field - OK to call

    void method(Parameter param, Factory factory) {

        // 1. Call methods on itself
        this.doSomething();                      // OK

        // 2. Call methods on parameters
        param.process();                         // OK

        // 3. Call methods on objects it creates
        Local local = new Local();
        local.execute();                         // OK

        // 4. Call methods on its fields
        helper.help();                           // OK

        // 5. Call methods on objects created by parameters
        Product product = factory.create();
        product.use();                           // OK (factory is param)

        // NOT OK: Chain through returned objects
        param.getChild().getGrandchild().doIt(); // VIOLATION!
    }
}

Fluent APIs Exception

Fluent APIs Exception


Refactoring Train Wrecks

// BEFORE: Train wreck violation
class InvoiceGenerator {

    void generate(Order order) {
        // Reaching deep into Customer
        String name = order.getCustomer().getName();
        String street = order.getCustomer().getAddress().getStreet();
        String city = order.getCustomer().getAddress().getCity();
        String country = order.getCustomer().getAddress().getCountry();

        // Create invoice with this data...
    }
}

// AFTER: Proper encapsulation

// Option 1: Delegate methods
class Order {
    String getCustomerName() { return customer.getName(); }
    String getBillingAddress() { return customer.getFormattedAddress(); }
}

class InvoiceGenerator {
    void generate(Order order) {
        String name = order.getCustomerName();
        String address = order.getBillingAddress();
    }
}


// Option 2: Create data transfer object
record InvoiceData(
    String customerName,
    String billingAddress,
    Money total
) {}

class Order {
    InvoiceData getInvoiceData() {
        return new InvoiceData(
            customer.getName(),
            customer.getFormattedAddress(),
            total
        );
    }
}

class InvoiceGenerator {
    void generate(Order order) {
        InvoiceData data = order.getInvoiceData();
        // Use data directly, no reaching needed
    }
}


// Option 3: Tell, don't ask
class Order {
    Invoice generateInvoice() {
        return Invoice.builder()
            .customer(customer.getName())
            .address(customer.getFormattedAddress())
            .total(total)
            .build();
    }
}

class InvoiceService {
    void process(Order order) {
        Invoice invoice = order.generateInvoice();
        invoiceRepository.save(invoice);
    }
}

When to Ignore LoD

When LoD Can Be Relaxed


Tips & Tricks

Tips and Tricks