NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!

Lekce 2 - Návrhové vzory GRASP - Dokončení

V minulé lekci, Návrhové vzory GRASP, jsme si uvedli vzory přiřazení odpovědnosti. Začali jsme vzorem Controller, který odděluje logickou a prezentační část.

V dnešním tutoriálu se budeme zabývat dalšími vzory GRASP pro přiřazení odpovědnosti. Budou to například Creator, High cohesion, Indirection a další.

Seznam vzorů GRASP

Připomeňme si vzory GRASP:

  • Controller,
  • Creator,
  • High cohesion,
  • Indirection,
  • Information expert,
  • Low coupling,
  • Polymorphism,
  • Protected variations,
  • Pure fabrication.

Vzor Controller jsme probrali v lekci Návrhové vzory GRASP. Dnes si projdeme zbývající vzory.

Creator

Vzor Creator řeší, do které třídy bychom měli umístit kód k vytvoření instance nějaké jiné třídy. Mějme třídu A a B. Třída B instanciuje třídu A pokud:

  • je třída A její částí,
  • je třída A její závislostí,
  • má pro instanciaci třídy A dostatek informací,
  • třída B obsahuje třídu A.

Třída A je částí třídy B

Příkladem pro situaci, kdy třída A je částí třídy B by mohly být třídy Faktura a PolozkaFaktury. Jednotlivé instance položek faktury dává smysl vytvářet ve třídě Faktura, jelikož je její součástí. Třída Faktura zde má za položky odpovědnost:

Vzor Creator z GRASP - Ostatní návrhové vzory

Třída A je závislostí třídy B

V tomto případě si třída B vytvoří třídu A, pokud na ni závisí. Příkladem by mohla být například databáze podpisů, jejíž instanci si vytvoří třída Faktura, aby mohla na vygenerované faktuře zobrazit podpis:

Vzor Creator z GRASP a závislosti - Ostatní návrhové vzory

Pokud je daná závislost použita ještě jinde, je výhodnější nevytvářet stále nové instance závislosti, ale použít vzor Dependency Injection. Více v kurzu Dependency injection a softwarové architektury.

Třída B má dostatek informací pro instanciaci třídy A

Zde se jedná o výběr takové třídy B, do které zbytečně nemusíme natahovat další data, když jsou již k dispozici někde jinde. Jako příklad si uveďme rozhodování, zda třídu SeznamFaktur s fakturami zákazníka instanciujeme ve třídě SpravceFaktur nebo SpravceZakazniku. Podíváme se, která z tříd má všechny informace, které SeznamFaktur potřebuje. Pokud zde budeme potřebovat například všechny faktury a z nich vybrat faktury od určitého zákazníka, instanciujeme SeznamFaktur ve SpravceFaktur, jelikož v něm se faktury nacházejí:

Vzor Creator z GRASP a informace - Ostatní návrhové vzory

Třída B obsahuje třídu A

Pokud je třída A vnořená ve třídě B, měla by být také instance třídy A vytvářena třídou B. Nicméně vnořené třídy se nestaly příliš populárními.

UML diagram této situace vypadá následovně:

Vzor Creator z GRASP a vnořená třída - Ostatní návrhové vzory

High cohesion

Vysoká soudržnost znamená, že se naše aplikace skládá z rozumně velkých kusů kódu. Každý tento kód se zaměřuje na jednu věc. To je i jeden ze základních principů samotného OOP. Vysoká soudržnost úzce souvisí s nízkou provázaností (viz dále). Když sdružujeme související kód na jedno místo, snižuje se nutnost vazeb do dalších částí aplikace. Dalším souvisejícím vzorem je Law of Deméter, který v podstatě říká, že objekt by neměl "hovořit" s cizími objekty.

Příkladem vysoké soudržnosti je například soustředění funkcionality okolo uživatelů do třídy SpravceUzivatelu. Když by se přihlášení uživatele řešilo například ve třídě SpravceFaktur, kde je přihlášení třeba pro zobrazení faktur, a zrušení uživatelského účtu by se řešilo ve třídě Uklizec, která promazává neaktivní účty, porušovali bychom High cohesion (viz dále). Kód, který má být pospolu ve třídě SpravceUzivatelu, by byl rozházený různě po aplikaci, podle toho, kde je zrovna potřeba. Proto sdružujeme související kód na jedno místo, a to i když se tyto metody používají v aplikaci třeba jen jednou.

Indirection

Indirection je velmi zajímavý princip, se kterým jsme se již setkali u controlleru. Říká, že když vytvoříme někde v aplikaci umělého prostředníka, tedy třídu "navíc", může naši aplikaci paradoxně výrazně zjednodušit. U kontroleru jasně vidíme, že sníží počet vazeb mezi objekty. Za cenu pár řádek kódu navíc podpoříme znovupoužitelnost a lepší čitelnost kódu. Indirection je jeden ze způsobů, jak dosáhnout Low coupling (viz dále). Příklad jsme si již ukazovali u vzoru Controller.

Information expert

Informační expert je další poučka, která nám pomáhá se rozhodnout do jaké třídy přidáme metodu, atribut a podobně. Odpovědnost má vždy ta třída, která má nejvíce informací. Takové třídě potom říkáme informační expert a právě do ní přidáváme další funkcionalitu a data. O podobném principu jsme již mluvili u vzoru Creator v této lekci.

Low coupling

Low coupling je v podstatě totéž jako High cohesion, ale z jiného pohledu. V aplikaci bychom měli vytvářet co nejmenší počet vazeb mezi objekty, čehož dosáhneme chytrým rozdělením zodpovědnosti.

Jako odstrašující příklad si uveďme třídu Manager, ve které je umístěna logika pro práci se zákazníky, s fakturami, s logistikou, zkrátka se vším. Takovýmto objektům se někdy říká "božské" (god objects), protože mají příliš velkou odpovědnost a tím pádem vytvářejí příliš mnoho vazeb (takový Manager bude typicky používat velkou spoustu tříd, aby mohl fungovat takto obecně). Asi vás již napadlo, že takový manažer by pravděpodobně nešlo znovu použít v jiné aplikaci. V aplikaci není důležitý celkový počet vazeb, ale počet vazeb mezi dvěma objekty. Vždy se snažíme, aby třída komunikovala s co nejmenším počtem dalších tříd, proto bychom měli uvést třídy SpravceUzivatelu, SpravceFaktur, SpravceLogistiky a podobně.

Zde je diagram UML:

Low coupling vzor z GRASP - Ostatní návrhové vzory

Odstrašující příklad božského objektu při nedodržování Low coupling

A nemusíme zůstávat jen u tříd. Low coupling souvisí také například s dalšími praktikami ohledně pojmenovávání metod. Metodu bychom měli pojmenovávat co nejméně slovy a bez spojky "a". Metody delej() nebo naparsujAZpracujAVypis() signalizují, že toho dělají příliš.

Když jsme již zmínili božské objekty, uveďme si i opačný problém, který je tak zvaný Yoyo problém (problém joja). Při příliš drobné struktuře programu, příliš vysoké granularitě, často i nadužívání dědičnosti, je v programu tolik tříd, že programátor se musí stále přepínat dovnitř nějaké třídy, zjistit jak pracuje a vrátit se zpět. Tato akce může připomínat vrhání joja dolů a nahoru, znovu a znovu. Před dědičností se proto často preferuje skládání objektů.

Co se týká vazeb mezi objekty, měli bychom se také vyvarovat cyklickým vazbám, které jsou všeobecně považované za špatnou praktiku. To jsou případy, kdy třída A odkazuje na třídu B a ta odkazuje zpět na třídu A. Zde je někde v návrhu něco špatně. Cyklická vazba může být i přes více tříd.

Polymorphism

Ano, i polymorfismus je návrhovým vzorem. I když je nám v principu polymorfismus dobře známý, zopakujme pro úplnost, že se jedná nejčastěji o případ, kdy potomek upravuje funkcionalitu svého předka, ale zachovává jeho rozhraní.

Z programátorského hlediska se jedná o přepisování (override) metod předka. S objekty poté můžeme pracovat pomocí obecného rozhraní, ale každý objekt si funkcionalitu zděděnou od předka upravuje po svém. Polymorfismus nemusí být omezený jen na dědičnost, ale obecně na práci s objekty různých typů pomocí nějakého společného rozhraní, které implementují. Ukažme si pověstný příklad se zvířaty.
Každé zvíře má metodu mluv(), ale přepisuje si ji od předka Zvire, aby vydávalo specifický zvuk:

Zvířecí zvuky jako ukázka polymorfismu v OOP - Ostatní návrhové vzory

Další ukázkou se nabízí například předek pro formulářové ovládací prvky. Každý prvek poté přepisuje metody předka jako vykresli(), vratVelikost() a podobně podle toho, jak konkrétní potomci fungují:

Vzor Polymorphism z GRASP - Ostatní návrhové vzory

Protected variations

Protected variations bychom mohli přeložit jako chráněné změny. Praktika hovoří o vytvoření stabilního rozhraní na klíčových místech aplikace, kde by změna rozhraní způsobila nutnost přepsat větší část aplikace.

Uveďme si opět reálný příklad. V systému ITnetwork používáme princip Protected variations, konkrétně pomocí návrhového vzoru Adapter. Tím se bráníme proti změnám, které neustále provádí Facebook ve svém API. Přihlašování přes Facebook a podobné další integrace mají za následek zvýšení počtu a aktivity uživatelů, bohužel ovšem za cenu přepisování aplikace každých několik měsíců. Pomocí rozhraní FacebookManagerInterface se systém již nemusí nikdy měnit. Když vyjde nová verze, kdy Facebook zas vše předělá, pouze se toto rozhraní implementuje v jiné třídě, například FacebookManagerXX, kde XX je verze Facebook API. V systému se poté změní instance, která toto rozhraní implementuje. Rozhraní je samozřejmě možné definovat i pomocí polymorfismu a abstraktní třídy:

Vzor Protected variations z GRASP - Ostatní návrhové vzory

Pure fabrication

O Pure fabrication jsme již dnes také mluvili. Volně přeloženo jako "čistý výmysl" se jedná právě o třídy, které slouží pouze pro zjednodušení systému z hlediska návrhu. Tak jako byl controller případ indirection, tak je indirection případem Pure fabrication.

Servisní třídy mimo funkcionalitu aplikace snižují závislosti a zvyšují soudržnost.

V další lekci, Servant (Služebník), si ukážeme návrhový vzor Služebník, který přidává skupině tříd určitou funkčnost bez její přímé implementace do těchto tříd.


 

Předchozí článek
Návrhové vzory GRASP
Všechny články v sekci
Ostatní návrhové vzory
Přeskočit článek
(nedoporučujeme)
Servant (Služebník)
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
6 hlasů
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity