Law of Demeter

The Law of Demeter, often termed as the principle of “least knowledge”, is a design guideline for developing software, particularly with regard to object-oriented programming. The core idea of this law is to promote loose coupling between classes and objects. The basic principle can be summarized as follows:

  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: Only talk to immediate “friends”, which typically means methods of the same class or methods of closely related classes.
  3. Only talk to your immediate friends: A method of an object should invoke only the methods of:
    • The object itself
    • Objects passed in as parameters
    • Any object the method creates or instantiates
    • Any components of the object

In essence, the Law of Demeter suggests that a method M of an object O should only call the methods of the following kinds of objects:

  • O itself
  • M’s parameters
  • Any objects created/instantiated within M
  • O’s direct component objects

In a practical sense, this law aims to reduce dependencies between different parts of a code, making it more maintainable and less prone to bugs due to changes in unrelated system parts.

Examples in C++

Car context

Here’s a simple example to illustrate the Law of Demeter in C++:

Suppose we have a Car class, a Engine class, and a Piston class. According to the Law of Demeter, the Car class should not directly access the methods of the Piston class because it’s not a direct component of Car but rather a component of Engine, which in turn is a component of Car.

C++
class Piston {
public:
    void move() {
        // Piston movement logic
    }
};

class Engine {
private:
    Piston piston;
public:
    void start() {
        piston.move();  // This is okay, Engine knows about Piston
    }
};

class Car {
private:
    Engine engine;
public:
    void startEngine() {
        engine.start();  // This is okay, Car knows about Engine
    }
};

In this example:

  • Car can call methods on Engine because Engine is a direct component of Car.
  • Car does not call methods directly on Piston, adhering to the Law of Demeter, as Piston is not a direct component of Car.
  • Engine handles the interaction with Piston, maintaining encapsulation and reducing the coupling between Car and Piston.

This design helps in maintaining a modular structure where changes in the Piston class won’t affect the Car class directly, thus making the system more maintainable and scalable.

Hospital Context

Violation of the Law of Demeter

In this example, we’ll create a situation where a Hospital class directly accesses and manipulates the Patient objects’ data, violating the Law of Demeter.

C++
#include <iostream>
#include <string>
#include <vector>

class Patient {
public:
    Patient(const std::string& name) : name(name) {}

    void diagnose() {
        std::cout << name << " has been diagnosed." << std::endl;
    }

    const std::string& getName() const {
        return name;
    }

private:
    std::string name;
};

class Hospital {
public:
    void admitPatient(Patient& patient) {
        patients.push_back(patient);
        std::cout << "Admitted patient: " << patient.getName() << std::endl;
    }

    void diagnosePatients() {
        for (Patient& patient : patients) {
            // Violation of the Law of Demeter
            patient.diagnose();
        }
    }

private:
    std::vector<Patient> patients;
};

int main() {
    Hospital hospital;
    Patient patient1("Alice");
    Patient patient2("Bob");

    hospital.admitPatient(patient1);
    hospital.admitPatient(patient2);

    hospital.diagnosePatients();

    return 0;
}

In this code, the Hospital class directly calls the diagnose method of each Patient object in the diagnosePatients function. This violates the Law of Demeter because the Hospital class should not have to know the internal details of the Patient class.

Adherence to the Law of Demeter

In this improved version, we will ensure that the Hospital class does not directly manipulate the Patient objects’ data and instead interacts with them through a more abstract interface.

C++
#include <iostream>
#include <string>
#include <vector>

class Patient {
public:
    Patient(const std::string& name) : name(name) {}

    void diagnose() {
        std::cout << name << " has been diagnosed." << std::endl;
    }

    const std::string& getName() const {
        return name;
    }

private:
    std::string name;
};

class Hospital {
public:
    void admitPatient(Patient& patient) {
        patients.push_back(patient);
        std::cout << "Admitted patient: " << patient.getName() << std::endl;
    }

    void diagnosePatients() {
        for (Patient& patient : patients) {
            // Adhering to the Law of Demeter by not directly calling diagnose on patients
            performDiagnosis(patient);
        }
    }

private:
    std::vector<Patient> patients;

    void performDiagnosis(Patient& patient) {
        patient.diagnose();
    }
};

int main() {
    Hospital hospital;
    Patient patient1("Alice");
    Patient patient2("Bob");

    hospital.admitPatient(patient1);
    hospital.admitPatient(patient2);

    hospital.diagnosePatients();

    return 0;
}

In this improved version, the Hospital class adheres to the Law of Demeter by not directly calling the diagnose method on patients. Instead, it introduces a private performDiagnosis method that encapsulates the diagnosis behavior, ensuring that the Hospital class interacts with patients through a more abstract interface and doesn’t reach deep into the internal details of the Patient class.