Prawo Demeter Wasal mojego wasala nie jest moim wasalem
Jest to składowa zasada paradygmatu SOLID. Nazywana wieloma określeniami jak: "Nie rozmawiaj z obcymi" czy "Rozmawiaj tylko z przyjaciółmi". Ja jednak wole staropolskie, "Wasal mojego wasala nie jest moim wasalem", co idealnie pokazuje że od wieków mamy wpajane zasady dobrego programowania. Podobnie jak w opisywany przez mnie abstrakcjach chodzi o to aby nie mieszać warstw. Nie możesz tworzyć korelacji z odległymi obiektami. Jest wiele opisów tej zasady, ja swoim zwyczajem postaram się ją wytłumaczyć na zasadzie przykładów.
Definicja mówi że metoda m
należąca do klasy C
powinna wykonywać tylko metody należące do:
- Klasy
C
- Obiektu utworzonego przez metodę
m
- Obiektu który zostanie przekazany jako argument do metody
m
- Obiektu umieszczonego jako zmienna instancyjna klasy
m
Metoda należąca do klasy
W naszym królestwie mamy innych rycerzy których możemy poprosić o to aby coś dla nas zrobili. Przekładając na kod, możemy dowolnie używać metod należących do tej samej klasy:
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);
}
}
Metoda goodDemeterMethod
jest zgodna z prawem Demeter, ponieważ korzysta z metody należącej do tej samej klasy Loan
. Taką strukturę łatwo się czyta i nie musimy opuszczać ciała klasy aby sprawdzić co wywołana metoda robi.
Obiekt utworzony przez metodę
Królestwo naszych wasali może wezwać na pomoc rycerza z innego królestwa, musi się to jednak odbyć uroczyście w "zamku" wasala który wzywa na pomoc. Chodzi o to że możemy stworzyć obiekt ale tylko wewnątrz danej metody w której został on utworzony. Przykład:
class Product {
constructor(price) {
this.productPrice = price;
}
get price() {
return this.productPrice;
}
}
class Cart {
goodDemeterMethod() {
const car = new Product(100000);
}
}
Jak możemy zauważyć, nie stracimy dużo czasu na odnalezienie klasy dzięki której powstał obiekt wewnątrz metody goodDemeterMethod
.
Obiekt przekazany jako parametr
Ktoś z obcego dworu przybył do naszego zamku, grzechem byłoby go nie wykorzystać. Możemy użyć obiektu przekazanego do metody, przykładowo:
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);
Jak możemy zauważyć przekazaliśmy do metody goodDemeterMethod
opcje będące obiektem zewnętrznym, jeżeli będziemy czytali nasz kod będziemy w łatwy sposób wiedzieć jaki obiekt został przekazany wewnątrz metody. Możemy też zauważyć że przekazujemy do kolejnej metody setActiveSlide
tylko wartość, nie możemy korzystać wewnątrz niej z obiektu options
ponieważ łamałoby to zasady Demeter.
Obiektu jako zmienna instancyjna
Podczas tworzenia naszego królestwa, możemy stworzyć naszego specjalnego wasala. Czyli podczas tworzenia obiektu w konstruktorze możemy utworzyć obiekt z którego będziemy korzystali.
class Worker {
constructor(employee) {
this.employee = employee;
}
goodDemeterMethod() {
console.log(this.employee.age);
}
}
let developer = new Worker({
age: 38
});
developer.goodDemeterMethod();
Przekazaliśmy obiekt podczas inicjalizacji a następnie możemy go używać w metodzie goodDemeterMethod
.
Przykłady użycia
Ogólnie jeżeli możemy na pierwszy rzut oka powiedzieć skąd w naszej metodzie wziął się dany obiekt, najprawdopodobniej będzie to metoda zachowująca prawo Demeter. Jest to jednak trochę złudny sposób, gdyż czasami podstępni programiści ukrywają przed nami złamanie zasady Demeter, więc najlepiej zapamiętać zasady dotyczące tego prawa. Poniżej przyjrzyjmy się kilu przykładom, zanim przeczytasz opis przykładu postaraj się odpowiedzieć czy dana metoda spełnia założenia prawa 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("Dąb"));//Dąb
No i jak Twoje przypuszczenia? Czy powyższy kod jest zgodny z zasadami prawa Demeter? Otóż nie, w linii 24 odwołujemy się do get
obiektu do którego mamy dostęp za pośrednictwem innego obiektu. Sprawdź czy ten obiekt klasy Specie
spełnia te założenia. Dobrze, sprawdźmy kolejny kod:
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();
Teraz znów nie działa? Nie no, teraz jest dobrze! Ale jak to? Przecież 31 linia. Tak, podejście że należy uważać na łańcuchy jest przesadzone. Tutaj każde ogniwo łańcuch zwraca nam ten sam obiekt, dokładnie ten który utworzyliśmy wewnątrz metody. A to jak najbardziej dozwolone. Na koniec taki oczywisty przykład:
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) {
this.pricePerSquareMeter = pricePerSquareMeter;
}
calcTotalCost() {
return oakDoor.doorArea * this.pricePerSquareMeter * 0.0001;
}
get totalCost() {
return this.calcTotalCost();
}
}
const kowalski = new Order(23.20);
console.log(kowalski.totalCost);//6681.6
Tutaj niestety nie udało nam się spełnić założeń prawa Demeter. Zwróćmy uwagę na metodę w 24 linii, skąd się tam wziął obiekt który pozwolił nam wyliczyć powierzchnię drzwi? No właśnie z du... znikąd. W komentarzach postaraj się naprawić ten obiekt. A na dzisiaj to wszystko! Dzięki za uwagę :)