What is the Law of Demeter (LoD)?
The primary goal of LoD is to reduce dependencies between objects, making the code easier to modify, test, and read.
The principle states that a method should only interact with the following types:
- Itself (self)
- Its own member variables
- Its method parameters
- Objects it creates
In other words, you should not directly work with an object's sub-objects (chained calls), but instead provide intermediary methods.
Bad Example
class Engine {
start() {
console.log("Engine started");
}
}
class Car {
engine: Engine;
constructor() {
this.engine = new Engine();
}
getEngine(): Engine {
return this.engine;
}
}
class Driver {
drive(car: Car) {
car.getEngine().start(); // Bad practice, as Driver directly uses Engine
}
}
The issue here is that the Driver class depends on the Car's Engine sub-object, which violates the LoD principle. If tomorrow the Car changes and has a different structure, the Driver will also need to be modified.
Good Example
class Engine {
start() {
console.log("Engine started");
}
}
class Car {
private engine: Engine;
constructor() {
this.engine = new Engine();
}
startEngine() {
this.engine.start(); // Intermediary method
}
}
class Driver {
drive(car: Car) {
car.startEngine(); // Now `Driver` only knows about `Car`, not `Engine`
}
}
Here, the Driver only interacts with the Car and no longer has a direct connection to the Engine. If something inside the Car changes, the Driver does not need to know about it.
Advantages of LoD
- Reduces dependencies, making the code easier to modify.
- Improves modularity by ensuring loose coupling.
- Strengthens encapsulation by preventing objects from directly accessing other sub-objects.
- Makes the code more testable, as mocks and dependency injection become more effective.
When Should You Break LoD?
Maintaining LoD is very important, but it can sometimes complicate simple operations. If intermediary methods are redundant and simply transfer functionality without adding value, it might be worth breaking the principle.