Mirin webspace

Nejbohatší život má ten, kdo žije s minimem nároků

29. 10. 2008 - Komentáře (11) Ostatní PHP

Jak lépe používat gettry a settry

Narazil jsem na jeden už trochu starší článek, týká se sice hlavně Javy, ale i tak je poměrně zajímavý a protože problematice designu a návrhu aplikací se moc prostoru nevěnuje, rozhodl jsem se o hlavních myšlenkách toho, jak a proč používat nebo nepoužívat tzv. gettry a settry něco málo napsat.

Co jsou gettry a settry asi každý ví, ale jen pro formu to zopáknu. Je to dvojice public metod, které umožňují přístup a nastavení nějaké většinou private nebo protected členské proměnné.

class Citizen {
  private $name;
 
  public function setName($name) {
    $this->name = $name;
  }
 
  public function getName() {
    return $this->name;
  }
}

Na první pohled to vypadá v celku v pořádku a zdá se, že vše je v souladu s jedním z pravidel objektového programování a to zapouzdření a skrytí implementačních detailů tříd. Ale není tomu tak. Ve výše uvedeném případě je naprosto jedno, pokud bude proměnná $name deklarovaná jako public. Jediná výhoda je ta, že setter a getter jako funkce umožňují do budoucna modifikovat to, jak se proměnná nastavuje, případně jaká se vrací hodnota. V našem případě třeba zkontrolovat znaky, které může jméno obsahovat, převést počáteční písmeno na velké apod. Ale i tak tento přístup není dobrý.

K čemu u takto navržených tříd, které mají gettry a settry vede jejich použití v kódu? K tomu, že na mnoha místech programu se bude vyskytovat volání gettru a pokud něco v gettru změníte budete muset projít všechna místa, kde se getter volá a zkontrolovat, zda je všechno v pořádku.

Často tento problém vzniká a musím se přiznat, že i já sám to tak dost často dělám, když prostě z nedostatku času neděláte podrobnější analýzu toho, jak se mají třídy v programu chovat a prostě začnete rychle něco smolit, protože Vás jako obvykle tlačí čas. Docela dobrý princip, jak se vyhnout accessorům (jak se gettrům a settrům někdy souhrnně říká), je použít zásady:

Neptej se na informace, které potřebuješ k vykonání nějaké činnosti, místo toho požádej daný objekt, ať danou činnost vykoná.

Často dochází k použití accessorů u tříd, kde se moc nepřemýšlí nad tím, jaký budou mít třídy dynamický model, jak se budou chovat k ostatním třídám, jak si s nimi budou "povídat". Další dva důvody jsou uvažování "co kdyby" a zvyky z procedurálního způsobu programování.

Způsob "co kdyby" znamená asi to, když si řekneme, že se nám to někdy v budoucnu bude hodit, tak to tam plácneme. Je to ale zbytečné, dost často na to nedojde, zapomeneme na to, kód se stává měně přehledným a hůře se v něm orientuje a my nakonec implementací takových věcí ztrácíme zbytečně čas.

Procedurální způsob programování často vede k tomu, že člověk je zvyklý mít kontrolu nad spoustou věcí, u objektového přístupu se od toho člověk musí nějak oprostit, musí uvažovat v lokálních potřebách daného objektu a pokud možno se vyhnout globálním proměnným, funkcím atd.

Základem je určitě dobrá analýza toho, co má program dělat. Vše se má točit okolo tzv. use cases - příkladů užití. Je nutné rozlišit, co je use case a co není. Logování není use case, netýká se problému oblasti, kterou řešíme, vyplacení faktury je use case. Je tedy potřeba provést dobrou analýzu toho, co vlastně řešíme a podle toho navrhnout třídy a jejich metody, prostřednictvím nichž se budou objekty těchto tříd mezi sebou domlouvat. Množina tříd pak bude představovat něco jako statický model naší úlohy a to, jak se mezi sebou budou objekty tříd domlouvat pak dynamický model.

V dnešní době se k návrhu takových systémů používá zejména UML, výše uvedený článek uvádí docela zajímavou metodu kartiček, kterou používali pánové Beck a Cunningham na svých kurzech objektového návrhu už někdy před 20 lety. Je asi jedno, co se použije, každopádně výsledek bude většinou ten, že množství accessorů se jaksi samo od sebe minimalizuje.

Kdy accessory použít?

Existují tedy vůbec situace, kdy je vhodné používat gettry a settry? Asi ano. Je to zejména tam, kde se svět objektového systému potkává s procedurálním. Většina objektových programů potřebuje nějakým způsobem spolupracovat s klasickým procedurálním operačním systémem, relační databází apod. Rozhraní do těchto systémů jsou pak dost generická, pokud mají mít všeobecnou a flexibilní použitelnost. Pak jsou většinou koncipována na základě gettrů a settrů. Jedním takovým případem z Java světa je JDBC. To je zároveň důkazem toho, jak jsou takové věci problematické. Jen málokterý Javista dnes používá JDBC přímo, místo toho se používají různé nadstavby, ORM apod.

Co na závěr? Accessorů se asi nelze zbavit úplně, ale solidním objektovým návrhem je lze více méně úspěšně eliminovat na minimum. Navíc tím jako bonus získáme dobře spravovatelný a přehledný celek. Je ale nutné přiznat, že udělat dobrý objektový návrh není žádná sranda a zvládá to málokdo, já osobně to tedy moc neovládám a tak ze mě leze poměrně dost kódu s gettry a settry, snad se to někdy časem zlepší.


Komentáře (11)

  1. eh - 30. 10. 2008 00:36

    Uvedený článek jsem nečetl, ale přijde mi, že odsuzuješ gettery a settery jenom proto, že hrozí že je někdo "špatně" přepíše, což je hodně chabý důvod pro jejich nepoužívání. S tím že "získáme dobře spravovatelný a přehledný celek" by se taky dalo polemizovat.

  2. Jakub Podhorský - 30. 10. 2008 07:57

    K čemu u takto navržených tříd, které mají gettry a settry vede jejich použití v kódu? K tomu, že na mnoha místech programu se bude vyskytovat volání gettru a pokud něco v gettru změníte budete muset projít všechna místa, kde se getter volá a zkontrolovat, zda je všechno v pořádku.

    tomuhle se ovšem dá vyhnout dobrými unit testy

  3. koubel - 30. 10. 2008 08:31

    [2] - použitím unit testu se nevyhneš tomu, že budeš muset projít všechna místa, kde se ten problematický getter nachází a ručně to opravit, na něco pak můžeš zapomenout, atd.

  4. koubel - 30. 10. 2008 08:36

    [1] - já je úplně neodsuzuju, spíš jde o to, že pokud se zaměříme víc na to, co program skutečně má řešit a tomu uzpůsobíme návrh tříd a jejich metod, tak z toho většinou vyplyne to, že accessory nejsou moc potřeba.

  5. Petr P. - 30. 10. 2008 12:42

    [1] Drahý 'eh', vás příspěvek jsem nečetl přesto s vámi zásadně nesouhlasím. ;]

    (omlouvám se za OT, neodolal jsem)

  6. eh - 30. 10. 2008 13:50

    [5] Vysypte si popel na hlavu, nečetl jsem pouze ten anglický článek :)

    [4] Jojo chápu, nicméně problém vězí v tom, že nejsou _momentálně_ potřeba. Pokud někoho přepadne v další verzi touha je přidělat, tak skončí úplně stejně prohledáváním většiny kódu a přepisováním.

    Používáš ORM ? Devadesát procent mých tříd je generováno Propelem, který dělá akcesory všude (díkybohu). Nedokážu si představit, že bych nekonzistentně někde set/gettery používal a jinde ne, přičemž bych se ještě připravoval o tu jednoduchost změny díky promyšlené dědičnosti. Jo, je to přesně ten speciální případ o kterém píšeš v článku, nicméně já podobný "systém" začal používat i v ručně psaných třídách a nemám jediný problém - imho to chce spíš dodržovat určitá pravidla než se akcesorů preventivně zbavovat.

  7. Petr P. - 30. 10. 2008 14:36

    [6] V tom případě si sypu popel na hlavu ;] a omlouvám se za falešné nařknutí. Musíte ale uznat, že to k takové reakci vybízelo. ;]

  8. Jakub Podhorský - 30. 10. 2008 18:16

    [3] tak jsem to asi blbě pochopil ale pokud mám getter tak předpokládám co mi bude vracet i když je fakt že se to časem může změnit(i když ve většině případů opravdu nepatrně) v případě přímého volání instanční proměné k tomu dojde

    btw: pokud mám kód pokrytý unit testy tak při změně chování getteru se mi to prostě v testech projeví ale celkově mi přijdou změny jednodušší

  9. David Grudl - 30. 10. 2008 21:11

    Článek bych určitě k přečtení doporučil, protože nabádá k pokročilejšímu uvažování při návrhu objektového modelu aplikace. Ale zrovna to s těmi settery gettery svádí ke špatné interpretaci. Vždyť i zmíněné ORM je vlastně esence setterů a getterů.

    Autor chce říct, že pokud je jedním z pravidel OOP zapouzdření a skrytí implementačních detailů, tak papouškováním kódu, který na začátku uvedl Koubel, se toho nedosáhne.

  10. lzap - 13. 11. 2008 17:36

    Publikovaný kód je ovšem stále jemně řečeno "problematický". Nezapouzdřuje totiž stále objekt dobře. Pro volajícího totiž v publikovaném případě není problém změnit obsah referenčního typu členské proměnné - i když je private!

    Pro někoho to může být překvapením, ale u referenčních typů to jde:

    $objekt-getMember()-setNecoJineho()

    Tzn změníme stav objektu, ale pritom vubec nepouzijeme set metodu.

    Odpovědí je TZV DEFENIVNÍ PROGRAMOVÁNÍ a programování proti rozhraní...

  11. koubel - 13. 11. 2008 18:55

    [10] na to jsem narazil, když jsem dělal dříve v C++, tam bylo takovéto const, volatile, mutable atd., daly se vracet konstantí reference. V php (a asi ani v Javě), kde nic tu nic, zkusím na uvedené věci něco zagooglit, nějaké odkazy by nebyly?

Komentáře jsou uzavřeny.