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

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šší.

Seriál: Principy objektově orientovaného návrhu (12 dílů)

  1. Návrhové principy: SOLID 9.5.2012
  2. Návrhové principy: Deméteřin zákon 18.5.2012
  3. Návrhové principy: DRY 30.5.2012
  4. GRASP – 1 – Úvod a Protected variations 20.6.2012
  5. GRASP – 2 – High cohesion 2.7.2012
  6. GRASP – 3 – Low coupling 16.7.2012
  7. GRASP – 4 – Polymorphism, Pure fabrication a Indirection 30.7.2012
  8. GRASP – 5 – Information expert a Creator 15.8.2012
  9. GRASP – 6 – Controller a jak GRASP používat 17.9.2012
  10. GRASP – 7– Modelové příklady 15.10.2012
  11. Návrhové principy: Tvorba balíčků (1/2) – Soudržnost 14.11.2012
  12. Návrhové principy: Tvorba balíčků (2/2) – Závislosti a provázanost 14.1.2013

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

Martin Jonáš pracuje jako projektový manažer v GrowJOB institute. Vystudoval Informační systémy na Fakultě informačních technologií VUT.

Komentáře: 39

Přehled komentářů

PrymekM Skládání objektů
Martin Jonáš Re: Skládání objektů
PrymekM Re: Skládání objektů
Martin Jonáš Re: Skládání objektů
PrymekM Re: Skládání objektů
Martin Jonáš Re: Skládání objektů
bug_nazi Re: Skládání objektů
Rax Re: Návrhové principy: Deméteřin zákon
Martin Jonáš Re: Návrhové principy: Deméteřin zákon
Mirek Re: Návrhové principy: Deméteřin zákon
Rax Re: Návrhové principy: Deméteřin zákon
Martin Jonáš Re: Návrhové principy: Deméteřin zákon
Rax Re: Návrhové principy: Deméteřin zákon
Martin Jonáš Re: Návrhové principy: Deméteřin zákon
PrymekM Re: Návrhové principy: Deméteřin zákon
Opravdový odborník :-) Re: Návrhové principy: Deméteřin zákon
Ivan Re: Návrhové principy: Deméteřin zákon
Ivan Nový Re: Návrhové principy: Deméteřin zákon
Karel Radikální všelék
Martin Jonáš Re: Radikální všelék
kert Re: Radikální všelék
Rene.Stein Demeteřin zákon, spíš doporučení
kert Re: Demeteřin zákon, spíš doporučení
Martin Jonáš Re: Demeteřin zákon, spíš doporučení
Rene.Stein Re: Demeteřin zákon, spíš doporučení
David Grudl Bičík
Martin Jonáš Re: Bičík
Ivan Nový Re: Bičík
balaton formatovanie
Martin Hassman Re: formatovanie
Opravdový odborník :-) Re: formatovanie
Rene.Stein Další poznámky
Martin Jonáš Re: Další poznámky
Rene.Stein Re: Další poznámky
Martin Jonáš Re: Další poznámky
Leoš Doopravdy máme povoleno volat metody objektů předaných jako argument?
Martin Jonáš Re: Doopravdy máme povoleno volat metody objektů předaných jako argument?
Bohouš Re: Doopravdy máme povoleno volat metody objektů předaných jako argument?
mishak Špatné pochopení zadání
Zdroj: https://www.zdrojak.cz/?p=3659