Demeter's Law A vassal of my vassal is not my vassal
This is a component of the SOLID paradigm. It is referred to by many names such as "Don't talk to strangers" or "Only talk to friends." However, I prefer the Old Polish one, "My servant's servant is not my servant," which perfectly shows that we have had the principles of good programming ingrained in us for centuries. Similarly, in the abstractions I describe, the idea is not to mix layers. You cannot create a relationship with distant objects. There are many descriptions of this principle, and as I usually do, I will try to explain it through examples.
The definition says that the method m
belonging to the class C
should only perform methods belonging to:
- Class
C
- An object created by the method
m
- An object that will be passed as an argument to method
m
- An object placed as an instance variable of the class
m
Method Belonging to a Class
In our kingdom, we have other knights whom we can ask to do something for us. Translated into code, we can freely use methods belonging to the same class:
class Loan {
#loanInterestPerYear;
#numberInstallmentsDuringYear;
#amountCredit;
constructor() {
this.#loanInterestPerYear = 0.24;
this.#numberInstallmentsDuringYear = 12;
this.#amountCredit = 100000;
}
goodDemeterMethod() {
return this.calcMonthlyInterest() * this.#loanInterestPerYear;
}
calcMonthlyInterest() {
return this.#amountCredit * (this.#loanInterestPerYear / this.#numberInstallmentsDuringYear);
}
}
The method goodDemeterMethod
is in compliance with the Demeter law because it uses a method belonging to the same Loan
class. Such a structure is easily readable and we do not have to leave the body of the class to check what the invoked method does.
Object Created by Method
Our vassal kingdom can call a knight from another kingdom to aid them, but it must be done ceremoniously at the "castle" of the vassal who calls for help. This means that we can create an object, but only within the method in which it was created. Example:
class Product {
constructor(price) {
this.productPrice = price;
}
get price() {
return this.productPrice;
}
}
class Cart {
goodDemeterMethod() {
const car = new Product(100000);
}
}
As we can see, we won't waste much time trying to find the class that created the object within the goodDemeterMethod
method.
Object Passed as Parameter
Someone from a foreign court has come to our castle, it would be a sin not to use them. We can use the object passed to the method, for example:
const options = {
firstSlide: 1,
numberOfSlides: 4
}
class Slider {
goodDemeterMethod(options) {
this.setActiveSlide(options.firstSlide);
}
setActiveSlide(index) {
console.log(index);
}
}
const slider = new Slider();
slider.goodDemeterMethod(options);
As we can see, we passed the options object to the goodDemeterMethod
method, if we read our code, we will easily know which object was passed inside the method. We can also see that we are passing only the value to the next method setActiveSlide
, we cannot use the options
object inside it because it would break the Demeter rules.
Object as an instance variable
While creating our kingdom, we can create our special vassal. That is, while creating an object in the constructor, we can create an object that we will use.
class Worker {
constructor(employee) {
this.employee = employee;
}
goodDemeterMethod() {
console.log(this.employee.age);
}
}
let developer = new Worker({
age: 38
});
developer.goodDemeterMethod();
We passed the object during initialization and then we can use it in the goodDemeterMethod
method.
Usage examples
Generally, if we can tell at first glance where an object in our method came from, it's probably a method that follows the Law of Demeter. However, this is somewhat a deceptive way, as sometimes sneaky programmers hide a violation of the Demeter rule from us, so it's best to remember the rules regarding this law. Below let's look at a few examples, before reading the example description try to answer whether the given method meets the assumptions of the Law of Demeter.
class Specie {
constructor(name) {
this.name = name
}
get specieName() {
return this.name;
}
}
class Plant {
constructor(name) {
this.name = name;
this.specie = new Specie(name);
}
get plantSpecie() {
return this.specie;
}
}
class Tree {
printPlantSpecie(plant) {
console.log(plant.plantSpecie.specieName);
}
}
const oak = new Tree();
oak.printPlantSpecie(new Plant("Oak"));//OAK
So how are your assumptions? Is the code above compliant with the Law of Demeter rules? Well, no, in line 24 we refer to the get
object that we have access to through another object. Check if the Specie
class object meets these assumptions. Let's check the next code:
class Library {
constructor(element) {
this.element = element;
}
active() {
this.element.classList.add("active");
return this;
}
load() {
this.element.classList.add("load");
return this;
}
change() {
this.element.classList.add("change");
return this;
}
}
class Slider {
constructor(element) {
this.querySelector = document.querySelector(element);
}
active() {
const library = new Library(this.querySelector);
library.active().load().change();
}
}
const slider = new Slider("body");
slider.active();
Is it working again? No, now it's fine! But how is that? After all, the 31st line. Yes, the approach that you need to be careful with chains is exaggerated. Here, every link in the chain returns us the same object, exactly the one that we created inside the method. And that is perfectly allowed. In the end, such an obvious example:
class Door {
constructor(width, height) {
this.width = width;
this.height = height;
}
get doorArea() {
return this.area();
}
area() {
return this.height * this.width;
}
}
const oakDoor = new Door(1200, 2400);
class Order {
constructor(pricePerSquareMeter, door) {
this.pricePerSquareMeter = pricePerSquareMeter;
this.door = door;
}
calcTotalCost() {
return this.door.doorArea * this.pricePerSquareMeter * 0.0001;
}
get totalCost() {
return this.calcTotalCost();
}
}
const kowalski = new Order(23.20, oakDoor);
console.log(kowalski.totalCost);//6681.6
Unfortunately, we were unable to meet the requirements of the Demeter law here. Let's take a look at the method in line 24, where did the object that allowed us to calculate the door area come from? Well, from no...where. In the comments, try to fix this object. That's it for today! Thank you for your attention :)