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:
- Each unit should have only limited knowledge about other units: Only units closely related to the current unit.
- 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.
- 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
.
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 onEngine
becauseEngine
is a direct component ofCar
.Car
does not call methods directly onPiston
, adhering to the Law of Demeter, asPiston
is not a direct component ofCar
.Engine
handles the interaction withPiston
, maintaining encapsulation and reducing the coupling betweenCar
andPiston
.
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.
#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.
#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.