W dobrze zaprojektowanym obiekcie najważniejsze jest to, że zna on tylko to, czego naprawdę potrzebuje. Dzięki temu kod łatwiej refaktoryzować, testować i rozbudowywać bez strachu, że zmiana jednego detalu rozsypie pół aplikacji. Właśnie temu służy zasada Demeter, znana też jako law of demeter: pomaga ograniczać nadmierne zaglądanie do wnętrza innych obiektów i utrzymywać odpowiedzialności tam, gdzie faktycznie powinny leżeć.
Najważniejsze wnioski w skrócie
- Ta zasada nie zabrania współpracy między obiektami, tylko każe ograniczać się do bezpośrednich zależności.
- Najczęstszy sygnał ostrzegawczy to długie łańcuchy wywołań metod i sięganie przez obiekty po cudze wnętrze.
- W praktyce najlepiej działa tam, gdzie logika biznesowa zaczyna mieszać się z detalami struktury danych.
- Najlepsze refaktoryzacje to te, które przenoszą zachowanie bliżej danych, a nie dokładują kolejne warstwy pośrednie bez sensu.
- To reguła projektowa, a nie święty zakaz: czasem prosty dostęp do danych jest rozsądniejszy niż sztuczne komplikowanie kodu.
Na czym polega zasada Demeter
Patrzę na nią jak na praktyczną wskazówkę: obiekt powinien rozmawiać tylko z tym, co zna bezpośrednio. Jeśli klasa ma referencję do użytkownika, to powinna używać użytkownika, a nie przeprawiać się przez użytkownika do adresu, do miasta, do kodu pocztowego i dopiero tam szukać danych potrzebnych jednej metodzie.
To nie jest zakaz współpracy obiektów. To raczej obrona przed sytuacją, w której jedna część systemu zaczyna znać zbyt dużo o strukturze innej części. Wtedy zmiana w środku modelu wymusza poprawki w wielu miejscach, a kod przestaje być odporny na rozwój.
W praktyce pomaga mi tu proste pytanie: czy ta metoda naprawdę potrzebuje tego konkretnego obiektu, czy tylko wyciąga przez niego coś z głębi modelu? Jeśli to drugie, zwykle da się przeprojektować interfejs tak, żeby odpowiedzialność wróciła na właściwy poziom. Dzięki temu łatwiej zachować enkapsulację, czyli ukrywanie szczegółów implementacji za stabilnym interfejsem.
Ta reguła jest szczególnie sensowna w systemach obiektowych, gdzie obiekty mają zachowanie, a nie są tylko workami na dane. Gdy już widzę ten kontekst, przechodzę do drugiego pytania: po czym konkretnie rozpoznać, że kod zaczyna ją łamać.

Po czym poznaję naruszenie zasady w kodzie
Najbardziej oczywisty sygnał to zbyt głębokie łańcuchy wywołań. Kod w stylu order.getCustomer().getAddress().getZipCode() zwykle mówi mi jedno: metoda wie za dużo o wnętrzu kilku obiektów naraz. Sama liczba kropek nie jest problemem matematycznym, ale jeśli z poziomu jednej klasy sięgasz do kilku warstw obcego modelu, to rośnie sprzężenie, czyli zależność od szczegółów, które powinny być ukryte.
Ja zwykle sprawdzam to tak: jeśli przy refaktoryzacji muszę poprawiać kilka miejsc tylko dlatego, że zmieniła się struktura jednego obiektu, to prawdopodobnie kod był zbyt ciekawski. Dobry interfejs powinien odpowiadać na intencję, a nie wymuszać eksplorację struktury.
| Przykład | Co mnie niepokoi | Lepszy kierunek |
|---|---|---|
order.getCustomer().getAddress().getCity() |
Logika biznesowa zależy od wnętrza kilku obiektów naraz. |
order.shippingCity() albo metoda, która jasno opisuje intencję. |
session.user.profile.company.plan.name |
Widok lub kontroler zna zbyt głęboką strukturę danych. | Spłaszczony model widoku lub metoda zwracająca potrzebny atrybut. |
invoice.getClient().getAccount().getBalance() |
Jedna klasa zaczyna pełnić rolę przewodnika po cudzym modelu. | Przenieść zachowanie bliżej źródła danych albo wystawić prostsze API. |
Warto tu dodać ważne zastrzeżenie: nie każdy chaining jest błędem. W kodzie prezentacji albo w prostym odczycie danych z DTO dłuższy zapis bywa po prostu najczytelniejszy. Problem zaczyna się wtedy, gdy taka składnia wchodzi do logiki domenowej i staje się sposobem na obchodzenie właściwych odpowiedzialności. To właśnie ten moment odróżnia świadome użycie od złego projektu, dlatego kolejnym krokiem pokazuję, jak ja to refaktoryzuję w praktyce.
Jak refaktoryzuję głębokie łańcuchy wywołań
Nie próbuję na siłę usuwać każdej kropki. Zamiast tego szukam miejsca, w którym zachowanie powinno zostać przeniesione bliżej danych. Najczęściej działają mi trzy ruchy: dodanie metody opisującej intencję, przekazanie właściwej zależności bez pośredników albo przesunięcie odpowiedzialności do obiektu, który już zna całą potrzebną strukturę.
// Słabo: kod wie za dużo o strukturze obiektów
const city = order.getCustomer().getAddress().getCity();
// Lepiej: obiekt zamówienia sam odpowiada na pytanie o miasto dostawy
const city = order.shippingCity();W tym drugim wariancie nie ukrywam problemu pod kolejną warstwą przypadkowych metod. Daję obiektowi semantykę, czyli nazwę operacji, która mówi, po co to wywołuję, a nie jak głęboko mam się przebić przez model.
Jeżeli metoda zaczyna robić się zbyt cienka i tylko deleguje dalej, to patrzę, czy to jeszcze poprawia czytelność, czy już tylko maskuje złożoność. Wrapper bywa dobry, gdy upraszcza użycie i zamyka spójne zachowanie. Jest słaby, gdy staje się sztucznym tunelem do cudzego wnętrza.
// Zamiast:
if (user.getProfile().getSettings().isMarketingOptIn()) {
sendNewsletter(user);
}
// Wolę:
if (user.allowsMarketing()) {
sendNewsletter(user);
}Ten sposób refaktoryzacji działa najlepiej, gdy umiesz nazwać zachowanie jednym zdaniem. Jeśli nie umiesz, to często znak, że problem leży głębiej: albo model jest źle podzielony, albo obiekt ma za dużo odpowiedzialności. Po tej zmianie łatwiej wejść w kolejny temat, czyli gdzie ta reguła daje największy zwrot w architekturze webowej.
Gdzie ta reguła najlepiej działa w architekturze webowej
W aplikacjach webowych szczególnie pilnuję jej na granicy między warstwami. Kontroler nie powinien przekopywać się przez kilka encji tylko po to, by zbudować odpowiedź HTTP. Serwis aplikacyjny nie powinien znać wszystkich szczegółów obiektów domenowych, jeśli wystarczy mu jedno wyraźne polecenie. Komponent UI też nie musi wiedzieć, jak głęboko schowane są dane, jeśli można podać mu gotowy model widoku.
| Warstwa | Co zwykle robię | Czego unikam |
|---|---|---|
| Kontroler | Zbieram dane wejściowe i przekazuję je do serwisu. | Składam logikę biznesową z kilku zagnieżdżonych obiektów. |
| Serwis aplikacyjny | Orkiestruję proces i proszę domenę o konkretną akcję. | Przebijam się przez kolejne poziomy encji zamiast wywołać właściwe zachowanie. |
| Widok lub komponent | Pracuję na modelu dopasowanym do prezentacji. | Rozbieram encję na czynniki pierwsze w samym interfejsie użytkownika. |
W praktyce oznacza to, że w dobrym projekcie często pojawiają się obiekty typu DTO, view model albo command. To nie są byty stworzone po to, by sztucznie komplikować kod, tylko po to, by oddzielić dane potrzebne do konkretnego zadania od pełnej struktury domeny. Dla mnie to jedna z najbardziej opłacalnych decyzji w większych projektach, bo zmniejsza ryzyko przypadkowych zależności.
Nie stosuję jednak tej reguły jak kijka do każdego fragmentu kodu. Są sytuacje, w których prosty dostęp do zagnieżdżonej właściwości jest po prostu najbardziej czytelny. Dlatego dobrze znać też granice i typowe pomyłki.
Najczęstsze błędy i granice stosowania
Największy błąd widzę wtedy, gdy ktoś traktuje tę regułę jak zakaz używania kropek w ogóle. To prowadzi do absurdów: pojawiają się zbędne metody pośrednie, klasy-opakowania i fałszywe abstrakcje, które tylko rozciągają kod bez realnego zysku. Wtedy zamiast prostoty mamy teatralną architekturę.
Drugi częsty problem to mylenie reguły z dogmatem. Jeśli pracujesz na prostym obiekcie danych, na przykład na płaskim widoku lub na strukturze do serializacji, to nie ma sensu walczyć z naturalnym dostępem do pól. W takich miejscach ważniejsza bywa czytelność i lokalna prostota niż rygorystyczne rozdzielanie wszystkiego na siłę.
Trzecia pułapka to tworzenie interfejsów tak szerokich, że obiekt zaczyna udawać pośrednika do całego świata. Zamiast lepszego projektu dostajesz klasę, która ma po jednej metodzie do każdego przypadku użycia. Taki efekt bywa nawet gorszy niż pierwotne zagnieżdżenie, bo ukrywa złożoność pod rozrośniętym API.
Ja przyjmuję prostą granicę: jeśli nowa metoda naprawdę opisuje zachowanie domenowe, to ma sens. Jeśli ma tylko przepchnąć wywołanie o jeden poziom niżej, zwykle nie wnosi niczego poza większą liczbą plików. Właśnie dlatego sensowne stosowanie tej zasady wymaga nie ślepej dyscypliny, lecz rozumienia intencji kodu.
Gdy już to uporządkujesz, zostaje najciekawsza część: co realnie poprawia się w projekcie, kiedy zaczynasz pisać w ten sposób konsekwentniej.
Co zwykle poprawia się od razu po wdrożeniu tej reguły
Najpierw poprawia się czytelność. Kod przestaje wyglądać jak ścieżka przez cudze wnętrze i zaczyna opowiadać, co właściwie robi. Potem rośnie odporność na zmiany, bo modyfikacja jednego obiektu rzadziej wymaga poprawek w wielu miejscach naraz. To jest najważniejszy praktyczny efekt, a nie abstrakcyjna elegancja.
Druga korzyść to łatwiejsze testowanie. Gdy metody mają prostsze zależności, łatwiej je odseparować, podmienić albo sprawdzić w izolacji. W większych systemach to oszczędza dużo czasu, bo test nie musi budować całej struktury obiektów tylko po to, by sprawdzić jeden przypadek biznesowy.
Jeśli mam zacząć od jednego kroku, to wybieram najgłębsze łańcuchy w logice domenowej i pytam: czy ten fragment nie powinien po prostu dostać własnej metody? To zwykle daje szybki efekt bez przebudowy całej aplikacji. A gdy już taki pierwszy krok zadziała, łatwiej zauważyć miejsca, w których architektura naprawdę potrzebuje uproszczenia.
W praktyce ta zasada nie wygrywa dlatego, że jest efektowna, tylko dlatego, że zmusza do lepszych decyzji projektowych. I właśnie za to cenię ją najbardziej: porządkuje relacje między obiektami, zanim chaos zdąży rozlać się po całym kodzie.