Přejít k navigační liště

Zdroják » Různé » Návrhové principy: Deméteřin zákon

Návrhové principy: Deméteřin zákon

Články Různé

Deméteřin zákon (Law of Demeter) je další z důležitých návrhových principů. Tento princip definuje omezení v tom, s jakými objekty bychom měli přímo komunikovat a s jakými ne. Při dodržování těchto doporučení je výsledný kód mnohem méně vzájemně provázaný a jeho udržování je mnohem jednodušší.

Deméteřin zákon (Law od Demeter – LoD)

Deméteřin zákon nám poskytuje pravidlo ohledně toho, komu (jakým objektům) může objekt zasílat zprávy (tedy volat metody, číst vlastnosti atd.). Někdy bývá také nazýván jako Pravidlo nejmenší znalosti (Principle of least knowledge).

Jeho oficiální změní je:

Metoda f třídy C by měla volat jen:

  • Metody třídy C
  • Metody objektů vytvořených metodou f
  • Metody objektů předaných jako argumenty metodě f
  • Metody objektů, které jsou instanční proměnnou třídy C

Někdy se toto formální znění parafrázuje jako „rozmlouvej s přáteli, ne s cizinci“, kde za cizince se považují objekty, ke kterým je nutné získat přístup přes prostředníka.

Motivací tohoto pravidla je co nejpřísnější ukrytí vnitřní struktury objektů, které vede ke snížení provázanosti tříd. Při aplikaci tohoto pravidla každá metoda ví pouze o několika málo metodách blízce příbuzných tříd.

Poznámka: Název je někdy překládán jako „Deméterův zákon“, jde ale o chybný překlad. Název pochází z výzkumného projektu zaměřujícího se na adaptivní a aspektově orientované programování na Northeastern University vedeného Dr. Karlem Lieberherrem, který byl pojmenován dle řecké bohyně Deméter.

Příznaky porušení

Existuje několik typických příznaků upozorňujících na porušení Deméteřina zákona.

Zřetězené gettery

value = object.getX().getY().getTheValue();

Pokud se v kódu vyskytne řetěz getterů jako v příkladu výše, jde obvykle o porušení Deméteřina zákona. Tento kód volajícího objektu závisí na vnitřní struktuře volaného. Jakékoliv její změny jsou potenciálním problémem, vyžadujícím zásah do všech míst, kde je podobným způsobem používán.

Přemíra pomocných proměnných

x = object.getX();
y = x.getY();
value = y.getValue();

Jde v podstatě jen o jinou formu zápisu stejné sekvence volání jako v předchozím případě. Zřetězené volání jen není na první pohled tak patrné, a proto se obtížněji odhaluje.

Datové struktury nejsou porušením

Street = User.address.street;

Za porušení Deméteřina zákona se nepovažují případy, kdy objekt, se kterým pracujeme, vystupuje pouze jako datová struktura s více úrovněmi zanoření. V takovém případě je jeho vnitřní struktura považována za veřejnou a není potřeba na něj aplikovat Deméteřin zákon.

Příklad

Jak by tedy měla vypadat aplikace Deméteřina zákona v praxi? Ukážeme si to na konkrétním příkladu.

Uvažujme třídu Customer představující zákazníka a třídu Wallet představující jeho peněženku:

public class Customer {
  private String name;
  private Wallet myWallet;

  public String getName(){
    return name;
  }

  public Wallet getWallet(){
    return myWallet;
  }
}
public class Wallet {
  private float value;

  public float getTotalMoney() {
    return value;
  }

  public void setTotalMoney(float newValue) {
   value = newValue;
  }

  public void addMoney(float deposit) {
   value += deposit;
  }

  public void subtractMoney(float debit) {
   value -= debit;
  }

}

Metoda vyžadující po zákazníkovi platbu pak bude vypadat nějak takto:

payment = 2.00;
Wallet theWallet = myCustomer.getWallet();
if (theWallet.getTotalMoney() > payment) {
  theWallet.subtractMoney(payment);
  //platba probehla uspesne
} else {
  // platbu nelze provest
}

Jak je vidět tento kód porušuje Deméteřin zákon, protože volá metody ( getTotalMoney(), subtractMoney()) objektu Wallet, který nespadá do žádné z kategorií povolených Deméteřiným zákonem, ale který je naopak součástí vnitřní struktury objektu  Customer.

To je problém z několika důvodů. Prvním z nich je vznik závislosti na třídě Wallet. Tato závislost způsobí, že v případě změny třídy Wallet bude nutné najít všechna místa, kde je obdobným způsobem zpracovávána platba a opravit je.

Druhým problémem je, že třída Customer ztrácí kontrolu nad tím, co se s její peněženkou stane. Volající metoda může s objektem Wallet provést v podstatě cokoliv, aniž by se o tom třída Customer dověděla.

Posledním častým problémem jsou změny vnitřní logiky třídy Customer. Řekněme, že v určitém případě budeme chtít vytvořit zákazníka bez peněženky (byla mu ukradena, nemá oprávnění k platbám, apod.). Nejjednodušším způsobem jak toho docílit, je například nastavit myWallet na null. Při takovéto změně jsme ale nuceni najít všechny kódy pracující s třídou Customer a doplnit k nim kontrolu zda getWallet() nevrací null. (Pozn.: Tomu by se dalo vyhnout při použití návrhového vzoru Null object, ale tento příklad slouží pouze jako ilustrace.)

Dalším příkladem by mohlo být, že třídu Customer chceme rozšířit o možnost si při nedostatku financí půjčit. Kód ověřující nutnost půjčky a její provedení by nemohl být součástí třídy Customer, protože ta nemá o operacích probíhajících s peněženkou informace, ale pouze k ní poskytuje přístup. Tento kód by tak musel být doplněn na všechna místa, kde je vyřizována platba.

Pokud bychom tedy chtěli výše uvedený příklad upravit s přihlédnutím k Deméteřinu zákonu, upravili bychom třídu Customer následovně:

public class Customer {
  private String name;
  private Wallet myWallet;

  public String getName(){
    return name;
  }

  public bool getPayment(float bill) {
    if (myWallet != null && myWallet.getTotalMoney() > bill) {
      myWallet.subtractMoney(payment);
      return true;
    } else {
     return false;
  }

}

Volající kód by pak vypadal takto:

payment = 2.00; // “I want my two dollars!”
if (myCustomer.getPayment(payment)) {
  // platba probehla uspesne
} else {
  // platbu nelze provest
}

Příklad je volně převzat z http://www.ccs.neu.edu/re­search/demeter/demeter-method/LawOfDemeter/paper-boy/demeter.pdf

Výhody a nevýhody

Jak již bylo zmíněno dříve, hlavní motivací pro dodržování Deméteřina zákona je snižování provázanosti tříd. Díky tomu je omezen dopad změn při úpravách vnitřní struktury a fungování tříd. Pokud třída pouze zpřístupňuje svoji vnitřní strukturu, přesouvá tak odpovědnost za její udržování v konzistentním stavu na volajícího. Pokud je dodržen Deméteřin zákon, má třída potřebnou zodpovědnost a kontrolu svého vnitřního stavu.

Hlavní nevýhodou je, že třída musí poskytovat všechny potřebné metody pro svoje vnitřní objekty a manipulaci se svou vnitřní strukturou. Rozhraní třídy musí obsahovat mnohem více metod, než kdyby jen poskytovala přímý přístup ke svým vnitřním objektům. Rozhraní je pak složitější. Tuto nevýhodu ale do značné míry vyvažuje to, že oč je rozhraní třídy složitější o to bývá jednodušší kód třídu používající (viz výše uvedený příklad). Velká část logiky, kterou by musel obsluhovat volající, totiž je nebo může být uzavřena v rámci volané třídy.

Rozhraní třídy je pak také plošší a snadněji se v něm orientuje. Při používání třídy nemusíme zjišťovat, jaká je její vnitřní struktura, ale stačí nám znát pouze její rozhraní.

Existují případy, kdy rozhraní třídy záměrně pouze duplikuje část rozhraní vnitřní třídy. V těchto případech může dojít k situaci, kdy při změně rozhraní vnitřní třídy budeme nuceni změnu propagovat do všech rozhraní, které jsou z něj odvozena. Je tedy zhoršena udržovatelnost při změnách rozhraní.

Mohou nastat situace, kdy z logiky aplikace potřebujeme pracovat s vnitřními objekty jiné třídy – například pokud implementujeme Factory inicializující stav třídy. V takovém případě není nutné na dodržování Deméteřina zákona striktně trvat.

Závěr

Aplikace Deméteřina zákona vede k přehlednějšímu a čistšímu kódu. Udržování software je pak mnohem jednodušší a je sníženo riziko vzniku následných chyb.

Mnoho návrhových vzorů představuje konkrétní realizaci Deméteřina zákona. Známé návrhové vzory jako Adapter, Proxy, Decorator používají Deméteřin zákon ve smyslu skrytí obalovaných objektů a jejich zpřístupnění přes jiné (plošší) rozhraní. Návrhový vzor Facade je dalším příkladem, kde je vnitřní struktura tvořená několika třídami skryta za společné rozhraní.

Vzhledem ke zmíněným nevýhodám v některých případech může být porušení Deméteřina zákona ospravedlnitelné. Vždy bychom ale měli důsledně zvážit výhody a nevýhody z jeho porušení plynoucí.

Zdroje a další informace

Komentáře

Subscribe
Upozornit na
guest
39 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Mirek Prýmek

Přijde mi to jako staré dobré „kde můžeš použít skládání objektů, tam ho použij“. Mýlím se?

Martin Jonáš

Deméteřin zákon spíše řeší jak by mělo skládání objektů vypadat. Tedy pokud mám objekt složený s dalších objektů jak by mělo vypadat jeho rozhraní.

Mirek Prýmek

No pokud mám

private Wallet myWallet;
Wallet getWallet() {…}

tak to nepovažuju za skládání. Skládání je podle mě právě to, když buď metody vloženého objektu zopakuju do rozhraní vnějšího objektu, nebo mám mechanismus předávání neznámých zpráv.

Martin Jonáš

V tom případě akorát každý pod skládáním objektů rozumíme trochu něco jiného.

Vaše definice se s Deméteřiným zákonem v podstatě shoduje.

Mirek Prýmek

Mám za to (můžu se samozřejmě mýlit), že skládání se v učebnicích OOP uvádí jako mechanismus, pomocí kterého se dosahuje podobného efektu jako u dědičnosti, ale jinými prostředky.

Pokud bych pod skládáním myslel i to, že nějaký objekt vlastní jiný objekt (má ho v property), tak by skládáním bylo všechno a všude, protože (téměř) všechno je objekt :)

Martin Jonáš

No tak jak to chápu já skládání (kompozice) neimplikuje, že vnitřní objekt není z vnějšku dostupný. Ale tohle asi nemá cenu řešit. Jde jen o odlišné chápání jednoho pojmu (možná vycházíme z odlišné literatury). Na hlavní myšlence se shodujeme.

bug_nazi

– if (theWallet.get­TotalMoney() > payment) {
+ if (theWallet.get­TotalMoney() >= payment) {

Rax

To je zase takové plané konzultantské žonglování s pojmy.

Je sice krásné, že Wallet z vnějšku není vidět, jenže to pak implikuje duplikovat spoustu metod z Wallet i do Customer a to i když není jisté kolik z nich se využije. Pak když se změní Wallet, tak musím měnit i ty duplikované funkce v Customer, přičemž v praxi se ukázalo, že je levnější umožnit zvnějšku přístup k Wallet, než se pořád drbat s těmi duplikovanými funkcemi.

Pokud se vyskytne problém, tak právě na to byla vynalezena funkčnost friend function a friend class, kdy si Wallet a Customer mezi sebou vyřídí něco extra, ale toto extra není pro ostatní přístupné.

Závěr je tedy chybný. Správně to má být:

Aplikace Deméteřina zákona vede k přehlednějšímu a čistšímu kódu. Udržování software je pak mnohem mnohem složitější.

Martin Jonáš

Uvedenou nevýhodu v článku také uvádím – jde o nejčastější protiargument proti Deméteřinu zákonu. Nesouhlasím s Vámi ale v několika předpokladech:

1) Customer nemusí a ani by neměl slepě duplikovat metody z Wallet, které nemají z pohledu třídy Customer význam. Účelem třídy Customer není vystavit rozhraní třídy Wallet, ale poskytovat rozhraní pro práci se zákazníky. Samozřejmě existují případy, kdy je potřeba pouze vystavit celé rozhraní vnitřní třídy a v takovém případě je třeba se nad výhodností Deméteřina zákona zamyslet.

2) Změny rozhraní třídy Wallet se do rozhraní třídy Customer musí promítnout pouze v případě, že jde o duplikaci rozhraní 1:1. To by neměl být častý případ – třída Customer může mít vlastní rozhraní, které nemusí být zrcadlovým obrazem rozhraní Wallet. V takovém případě nám dodržení Deméteřina zákona práci ušetří – změny v souvislosti s rozhraním třídy Wallet provedeme na jednom místě (v třídě Customer) a ne na všech místech, kde se Wallet z Customer používá.

Jak uvádím v závěru: Vzhledem ke zmíněným nevýhodám v některých případech může být porušení Deméteřina zákona ospravedlnitelné. Vždy bychom ale měli důsledně zvážit výhody a nevýhody z jeho porušení plynoucí.

Mirek

Ty negativni vlastnosti mi pripadaji ponekud zvelicene.

Priklad v clanku ukazuje situaci Nakupu, kdy se krasne vydelil interface Zaplat, ktery lze prirozene implementovat, jak na Penezence tak na Zakaznikovi. A treba i na dalsich vecech jako Firma, Vlada, Stat. Podivejme, jak ta Demetra najednou pracuje, kdo vsechno muze v puvodnim Nakupu najednou nakupovat.

Jenomze tohle nemusi platit. Dejme tomu, ze by neslo o Nakup, ale o KapesniKradez, kdyz se Zlodej rozhoduje, jak s Penezenkou nalozi podle jejich vlastnosti. Potom by patricne rozhrani implementovane na Zakaznikovi bylo nesmyslne.

Demetra nas totiz v takovem pripade nevede k tomu implementovat rozhrani Penezenky na Zakaznikovi, ale naopak k tomu predat si jeho Penezenku nejak normalne (konstruktor, parametr), aby zavislost na ni byla zrejma a ne utajene ziskana.

Rax

1) Wallet vystavím ven z Customera například tehdy, že se mi nechce psát znovu těch 20 metod z Wallet. Pokud si vystačíte tak do tří pěti kusů metod, tak to pak vám věřím že se vám to líbí.

2) Když se změní třeba datový typ parametru nebo přidá nový parametr, tak to měním všude tak jako tak a s Demeterem ještě na více místech.

Netvrdím že Demeter je vysloveně špatně, ale připadá mi to jako Bronštejnova metoda z Básníků.

Martin Jonáš

ad 1) Zde bych se zamyslel zda pravdu potřebujete, aby Customer vystavil všech 20 metod z Wallet? Vystavovat všechny metody Wallet může totiž být chyba sama o sobě. Pokud je ale opravdu potřebujete všechny vystavit pak se může jednat právě o ten případ kdy je porušení Deméteřina zákona v pořádku.

ad 2) Zde bych se v první řadě zamyslel nad tím jak často měníte rozhraní oproti změnám chování. Jendo místo navíc, kde musím provést změnu při změně rozhraní, podle mě ve většině případů vyvažuje úbytek míst (na jedno), kde musím provést změnu při každé úpravě chování.

V druhé řadě mnoho změn rozhraní Wallet může bez problému pokrýt Customer tak, že jeho rozhraní zůstane beze změny (provede přetypování parametru, hodnotu dodatečného parametru je schopen sám určit, …). To je mimo jiné jedna z výhod, kterou Deméteřin zákon přináší – změny vnitřních objektů se nemusejí promítnout ven.

Samozřejmě existují případy, kdy nějaká změna v rozhraní způsobí nutnost upravit rozhraní všech odvozených tříd. Ve většině případů si ale myslím, že tato nevýhoda je více než vyvážena ostatními výhodami.

Určitě zle nalézt proti příklady, kdy tomu tak není a v takových je zcela správné Deméteřin zákon porušit. Jde spíš o to se vždy zamyslet zda k tomuto porušení mám dostatečně pádný důvod.

Rax

ad 1) Vždyť jsem to napsal, když vám stačí 3 metody, tak je to všechno fajn. Když potřebujete 20, tak se upíšete k smrti.

ad 2) To jsem psal už na začátku, v praxi se ukázalo levnější vystavit celý objekt a ne obráceně, tedy objekt schovat a psát na to extra metody. (Někdy to samozřejmě nejde, někdy se objekt musí schovat kvůli funkčnosti)

A Deméteřin zákon je zákonem jenom v republice konzultantů :-)

Martin Jonáš

Název „Law of Demeter“ je opravdu trochu nešťastný. Nejedná se rozhodně o zákon, který by nebylo možné porušit. Lepší by bylo používat (jak ve svém komentáři doporučoval René Stein) název „Deméteřino doporučení“.

Ohledně toho co se v praxi ukázalo levnější máme odlišné zkušenosti. Z vlastní praxe mohu říci, že by mi v mnoha případech práci ušetřilo, kdybych radši ty metody navíc napsal než pak při údržbě hledal všechna použití (ano i u nás tento zákon občas porušíme). Úprava kódu tak, aby splňoval Deméteřin zákon, je ale jeden z častých refaktoringů. Na druhou stranu souhlasím, že existují případy, kdy je porušení tohoto zákona opravdu výhodnější.

Striktně lpět na jakékoliv poučce je obvykle v návrhu SW chyba. Vždy je potřeba posuzovat konkrétní případ.

Mirek Prýmek

> jenže to pak implikuje duplikovat spoustu metod z Wallet i do Customer

Anebo použít pořádný OOP jazyk, který umí pohodlně předávat zprávy jinému objektu…

Opravdový odborník :-)

Ono i když to ten jazyk podporuje, tak je stejně potřeba řešit směrování různých zpráv ke správným vnořeným objektů, takže to není až taková výhra.

Ivan

Myslím, že problém je v míchání dvou věcí. Jde o to, které objekty jsou součástí struktury zákazníka, a které jen používá. To může být různé v různých aplikacích. Peněženka součástí struktury být nutně nemusí, adresa může.

Klientský kód komunikuje se zákazníkem. Chce od něj zaplatit. Kde si obstará zákazník obnos je jeho věc – je třeba, aby zaplatil, nikoliv aby měl peněženku. Pokud si budu od zákazníka vyžadovat peněženku, kladu si omezení – zákazník se bez peněženky neobejde (může být v některé aplikaci správně, to rozhoduje zadavatel).

Chce to tyto zákony znát. Neaplikovat je však mechanicky. Zvážit je-li dané místo případ, o němž zákon mluví. Listen to your code :)

Ivan Nový

Třída, která funkčně sdílí 20 metod s jinou třídou je špatně navržená. vyjmenujte mi nějaký nástroj z reálného světa, který má 20 funkcí. U Walletu jsou metody pouze tři. Vložit peníze, vybrat peníze, kolik je peněz v peněžence. Ale žádně zaplať, platí zákazník, ne peněženka, či účet. Logicky rozhraní Wallet nesmí obsahovat metodu Zaplať. V reálném světě, existuje vztah zákazník-peněženka, obchod-zákazník a ne vztah peněženka zákazníka-obchod. Porušení Deméřina zákona ukazuje na to, že software nesprávně modeluje poměry v reálném světě.

Karel

Chápu, že u všeho nového je potřeba udělat velký humbuk a všem opakovat, že konečně jsme vyřešili všechny neduhy a od teď vše bude úžasně snadné. Ale v případě tohoto zákona je to opravdu hodně odvážné tvrzení.

Základní metodou, kterou lidé zvládají komplexní problémy, je metoda „rozděl a panuj“. Udělám si objekty Customer a Wallet a budu si každý používat nezávisle na sobě. Protože spravovat dva různé objekty je pro mně jednodušší než jeden überobjekt. Věřím, že kód to zjednodušší a zpřehlední, ale člověk se v tom dlouhodobě nevyzná.

Nebo se článek snaží hodně nešikovnou formou říci, že pokud chci, aby zákazník zaplatil, tak se mu nemám sápat po peněžence, ale prostě po něm chtít peníze a nestarat se o to, kde je vezme? Protože na tomhle se shodneme. Pokud ale z nějakého důvodu budu chtít jeho peněženku (to asi ne, ale třebas kreditku bych rád viděl), tak si o ní řeknu a budu s ní pracovat (kupříkladu ověření PIN kreditky a transakce).

Svět není černobílý. A metody typu rozděl a panuj nebo modulární systémy jsou tu s námi již tisíce let. Využívali je už faraoni. Akorát se tomu tenkrát říkalo jinak, třebas dělba práce. V praxi to funguje dodnes, když firma A nakupuje od firmy B, tak to probíhá tím, že si nákupčí z firmy A najde kontakt na prodejce z firmy B a pošle mu objednávku. Podle Demeteřina zákona by si asi musel objednávat generální ředitel od generálního ředitele. Nebo by to byly recepční?

Martin Jonáš

Rozhodně se neodvažuji tvrdit, že Deméteřin zákon řeší všechny neduhy. Jak je v článku uvedeno jeho dodržování má i své nevýhody a v některých případech je jeho porušení výhodné. Návrh software je inženýrský disciplína – nikdy neexistuje „správné řešení“, ale několik „trochu špatných“ mezi kterými je nutné vybrat to nejméně špatné. Návrhové principy pouze poskytují vodítka jak se mezi nimi rozhodnout.

Deméteřin zákon Vám nijak nebrání používat objekty Customer a Wallet nezávisle. Pouze říká, že pokud je Wallet interní součástí Customer, mělo by se s ní pracovat prostřednictvím Customer, pokud není nějaký pádný důvod k ní přistupovat přímo.

„pokud chci, aby zákazník zaplatil, tak se mu nemám sápat po peněžence, ale prostě po něm chtít peníze a nestarat se o to, kde je vezme“ – ano i takhle by se to dalo parafrázovat. Prostě vykonat něco bych měl chtít po objektu se kterým pracuji ne po nějaké jeho vnitřní součásti. Vnitřní struktura objektu by měla být soukromá a z vnějšku bych o ní měl vědět co nejméně.

Důvody pro přímý přístup k vnitřním objektům samozřejmě existovat mohou. Smyslem Deméteřina zákona není zakázat z metod vracet vnitřní objekty v případech, kdy je to potřeba. Jeho smyslem je vnitřní strukturu objektu co nejvíce skrýt tak, aby se minimalizoval dopad jejích změn.

kert

Všechna tato pravidla a „zákony“ je třeba chápat jako zdroj inspirace, ne jako statement „dělejte to takhle a ne jinak, protože právě takhle a ne jinak je to správně“. V sw designu není žádná „objektivní pravda“, pro každý problém existuje více „správných“ cest jak ho řešit, přičemž výběr té konkrétní závisí na kontextu. Návrhové vzory jsou tu proto, aby začátečníkovi ukázaly cestu a pokročilého inspirovaly. Nic víc, žádné evangelium ani misie.
Pokud Vám ten misionářský tón vadí, nečtěte zjednodušené výcucy na rootu a raději si přečtěte přímo GoF Design Patterns, tam je to hezky probráno ze všech stran, pravidlo vedle protipravidla a příklad vedle protipříkladu, pěkně s náhledem.

René Stein

Nevím, jaké zkušenosti má autor s aplikací Demeteřina zákona, ale většina lidí velmi rychle pochopí, že slovo „zákon“ je nešťastné a že se jedná spíš o doporučení a také varování před některými konstrukcemi, které zvyšují razantním způsobem závislosti napříč systémem.

Také nevím, proč autor převzal v článku zmiňované znění Demetéřina doporoučení, protože na následující verzi je lépe vidět, jak je zákon problematický.

Toto je tzv. slabá verze „zákona“

Zákon DEMETER (s jakými objekty může metoda pracovat?)
Samotná instance (this)
Argument metody
Instanční proměnná včetně zděděných proměnných a objektů v kolekcích
Objekt, který metoda vytvoří
Globální objekt

Na wikipedii je to takto:
„O itself
M’s parameters
any objects created/instan­tiated within M
O’s direct component objects
a global variable, accessible by O, in the scope of M“

Skoro každý bod je problematický, každého asi uhodí do oči, že „zákon“, který má snižovat počet závislostí v systému spoléhá na globální proměnné.

Slabá je tato verze zákona proto, že v tzv. silnější variantě „zákona“ nemůžete používat zděděné proměnné/metody. To odpovídá puristickému pohledu, že dědičnost je apriori zlo a stejný purismus čiší i tohoto zákona. Navíc delegace na interní objekty a vystavení „makro-rozhraní“ skutečně nejsou všespásné metody a velmi rychle se dostanete do napětí třeba s principem, který říká, že třída má mít jednoduché rozhraní, které je v souladu s její odpovědností.

Já Demeteřino doporučení beru a i na svých kurzech ukazuju jako nepříliš výstižnou verbalizaci pravidla, že není vhodné tvrdit, že objednávka je „samostatnou, zapouzdřenou, znovupoužitelnou černou skříňkou, která se nestará o své okolí blablabla další OOPlky“, i když její veřejné rozhraní vypadá krásně, když uvnitř nalezneme *několik konstrukcí tohoto typu *.
Customer.Type­.Subtype.Defau­ltCategory.De­faultSubcatego­ry.Koeficient.

U takové třídy nejen že neplatí, že se nestará o své okolí, taková třída má naopak razantní předpoklady, jak vypadá (pravděpodobně) úplně jiná část systému. A většinou díky tomuto neklamenému indikátoru špatného návrhu rychle (třeba i v unit/integračních testech) zjistíte, že vám v systému nějaké entity / business služby a vztahy chybí.
Mezi anemickým doménovým modelem a použitelným business modelem totiž existuje spousta mezistupňů v podobě „kripl business modelů“ – a jeden kripl model může porušovat Demeteřin zákon a další dogmaticky naplňovat delegací. Cesta do objektového pekla je široká a rámována dobrými úmysly vykladačů OOP mystérií.:)

kert

:))

+1

Martin Jonáš

Nemohu než souhlasit.

Jak je vidět i z dalších komentářů tak uvést, že porušení tohoto zákona mají v některých případech smysl pouze v závěru bylo nedostatečné. Deméteřin zákon stejně jako všechny ostatní návrhové principy je nutné chápat vždy pouze jako doporučení. Ostatně většina jich je do určité míry ve vzájemném rozporu. Vždy je nutné použít zdravý rozum a dojít k vhodnému kompromisu.

Poznámka: Text zákona jsem převzal z knihy Čistý kód.

René Stein

.. A omlouvám se za chybějící čárky v některých vedlejších větách a za další překlepy, musím se odnaučit psát delší příspěvky na telefonu.:)

David Grudl

Na Deméteřin zákon je v poslední době populární bič používaný nesprávným způsobem. Společně s bičíky service locatorem a dědičností.

Já ho vždycky akceptoval ve zjednodušené (a možná nesprávné) interpretaci: pokud mám metodu, která chce provést platbu, tak ji nebudu předávat zákazníka-majícího-peněženku, ale rovnou peněženku.

Martin Jonáš

To je jeden ze způsobů jak se porušení Deméteřina zákona vyhnout – předávat jen to co je opravdu potřeba a ne prostředníka.

Ivan Nový

No a pak přijde exekutor s požadavkem, aby v peněžence zůstával zůstatek do výše pohledávky :-)))

balaton

nic v zlom, ale ked uz davam do clanku javovy kod, tak si aspon dam sakra pozor na formatovanie :)

Martin Hassman

Nic ve zlém, ale Java si žádný extraspeciální přístup nezaslouží 8-)

Opravdový odborník :-)

Souhlasím. Minimálně:

Street = User.address.stre­et;

by zasloužilo opravit. Nejde jen o tu Javu (i když snad každý jazyk si zaslouží, aby se dodržovaly jeho konvence), ale hlavně o čtenáře. Když člověk nemá při ruce IDE nebo kompletní zdroják, je dodržování konvencí dvojnásob důležité.

Velkým písmenem začínají názvy třídy, takže tady to vypadá, že addres je veřejná statická proměnná jakéhosi typu, který má jinou veřejnou vlastnost street. A to se snažíme pomocí operátoru = přiřadit do třídy (velké písmeno na začátku), což je nesmysl.

Takových zmatečných příkladů je lepší se zdržet a psát pořádně.

René Stein

Myslím si, že z velké části tato věta v článku není pravdivá.

„Mnoho návrhových vzorů představuje konkrétní realizaci Deméteřina zákona. Známé návrhové vzory jako Adapter, Proxy, Decorator používají Deméteřin zákon ve smyslu skrytí obalovaných objektů a jejich zpřístupnění přes jiné (plošší) rozhraní[…]“

Proxy (surrogate) nabízí stejné rozhraní jako reálný objekt – určitě není hlavní charakteristikou vzoru proxy, že nabízí „plošší rozhraní“. Klient si nesmí být vědom, že používá proxy a ne reálný objekt (role real subject ve vzoru).

U vzoru dekorátor mám tu samou výhradu. Dekorátor přidává chování a/nebo stav, ale originální třída ani zbytek aplikace nejsou existencí dekorátoru dotčeni – dekorátor má z hlediska aplikace stejné rozraní jako třída, kterou zapouzdřuje.

Vzor adaptér sice převádí cizorodé rozhraní, které aplikace neumí používat, na důvěrně známé aplikační rozhraní, ale nedá se říci, že by vždy rozhraní adaptéru mělo být „plošší“ – naopak někdy se stává, že rozhraní je opulentnější (místo celého csv řádku adaptér vystavuje typové vlastnosti).

Nebo sousloví „plošší rozhraní“ vyjadřuje něco jiného?

Martin Jonáš

Bylo tím myšleno, že implementace těchto návrhových vzorů splňuje Deméteřin zákon ve smyslu, že navenek skrývají svoji vnitřní strukturu a poskytují rozhraní v podobě metod obalového objektu namísto zpřístupnění vnitřních objektů. Deméteřin zákon zde není jako hlavní motivace vniku daných návrhových vzorů, ale jejich implementace jej dodržuje – tím, že navenek není přístupná vnitřní struktura objektu (obalovaný objekt).

Plošší rozhraní tedy bylo myšleno ve smyslu „voláme pouze metody jednoho objektu“ na jedné úrovni.

René Stein

Díky za doplnění. Přesto si to nemyslím:
“ tím, že navenek není přístupná vnitřní struktura objektu (obalovaný objekt).“
To je obyčejný popis zapouzdření.

Nebo abych to upřesnil – vaše chápání zákona Demeter je příliš široké.
Ve stejném duchu můžete pod Demeter subsumovat motivat pro zavedení vzorů:

1) Composite – s rozhraním složeného objektu se zachází stejně jako s rozhrnaím jednoduchého objektu a vnitřní struktura složeného objektu není patrná.

2) Iterator – jednotné rozhraní pro procházení složité struktury, aniž bych znal detaily.

3) Chain of responsibility – zavolám jednoduchou metodu pro zpracování požadavků a přitom nikde v řešení neprosvítá, jak je požadavek delegován na další členmy řetězu.

atd.

Jednoduše je (podle mě) zbytečné zmiňovat Demeteřino doporučení v tomto obecném kontextu, protože neexistuje žádná „specifická diference“, z níz by plynulo, že zrovna u těchto vámi vybraných vzorů se jedná o nějaký referenční případ aplikace „Demeteřina doporučení“.

Martin Jonáš

Uvedené vzory měly sloužit pouze jako příklady, kde lze v určité podobě Deméteřin zákon (doporučení) vidět, žádná „specifická diference“ tedy neexistuje jak říkáte.

Jak jsme uvedl v perexu předchozího článku: „Návrhové vzory jsou však jen konkrétní aplikace hlubších principů, na kterých by měl být objektově orientovaný návrh založen.“. U většiny návrhových vzorů tedy vždy v nějaké podobě tyto principy najdeme.

Co se týče uvedené souvislosti Deméteřina zákona a zapouzdření tak já Deméteřin zákon opravdu více méně chápu tak, že jde doporučení, jak správně zapouzdřovat objekty. Deméteřin zákon podle mě představuje konkrétní pravidlo jak se vyhnout narušení zapouzdření tím, že bych zpřístupnil vnitřní strukturu objektu.

Leoš

Porušuje následující přístup popisovaná pravidla? Připadá mi že podle uvedené definice ne, ale vzhledem k tomu že jde pouze o tupé přeuspořádání v rámci jednoho objektu, základní princip porušuje. Že by tedy ta definice byla špatně?

private boolean handleWallet(Wallet theWallet, double payment) {
if (theWallet.get­TotalMoney() < payment) return false;
theWallet.sub­tractMoney(pa­yment);
return true;
}

payment = 2.00;
if (handleWallet(my­Customer.getWa­llet(), payment)) {
//platba probehla uspesne
} else {
// platbu nelze provest
}

Martin Jonáš

Definici to formálně neporušuje, i když nejde zrovna o ideální způsob jak porušené Deméteřina zákona vyřešit.

V každém případě i uvedené přeuspořádání je zvýšením kvality kódu, protože uvedená metoda handleWallet může být použita vícenásobně a případná úprava chování by tedy byla soustředěna jen na jedno místo.

Umístění této metody do kontextu volajícího je ale chybou z jiného důvodu – pokud bychom stejné ošetření platby chtěli dělat v jiném objektu musíme danou metodu duplikovat a každá duplikovaný kód je chybou. Pokud je stejná metody umístěna v rámci třídy Customer jde o univerzálnější řešení.

Žádný návrhový princip nelze brát izolovaně od dalších principů a obecných zásad psaní kvalitního kódu. Při zvažování různých variant řešení je pak nutné porovnat varianty ve světle všech principů a vybrat tu nejvýhodnější.

Bohouš

Peníze jako double? Jen to ne!

mishak

Pokud chci provést platbu žádného zákazníka nepotřebuji. Proces platby a proces podání peněženky prodávajícímu jsou dva nezávislé procesy, které plno programátorů není schopno rozlišit v modelu od fyzického světa.

Nejspíš byl přidán, aby byl vytvořen slabý argument, proč Deméteřino pravidlo je důležité.

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.