2. diel - Návrhové vzory GRASP - Dokončenie
V dnešnom tutoriále sa budeme zaoberať ďalšími vzormi GRASP pre priradenie zodpovednosti. Budú to napríklad Creator, High cohesion, Indirection a ďalšie.
Zoznam vzorov GRASP
Pripomeňme si vzory GRASP:
- Controller,
- Creator,
- High cohesion,
- Indirection,
- Information expert,
- Low coupling,
- Polymorphism,
- Protected variations,
- Pure fabrication.
Creator
Vzor Creator
rieši, do ktorej triedy by sme mali umiestniť
kód na vytvorenie inštancie nejakej inej triedy. Majme triedu A
a
B
. Trieda B
inštanciuje triedu A
ak:
- je trieda
A
jej častí, - je trieda
A
jej závislosťou, - má pre inštanciáciu triedy
A
dostatok informácií, - trieda
B
obsahuje trieduA
.
A
je časťou triedy B
Príkladom pre situáciu, keď trieda A
je časťou triedy
B
by mohli byť triedy Faktura
a
PolozkaFaktury
. Jednotlivé inštancie položiek faktúry dáva
zmysel vytvárať v triede Faktura
, pretože je jej súčasťou.
Trieda Faktura
tu má za položky zodpovednosť:
Trieda A
je
závislosťou triedy B
V tomto prípade si trieda B
vytvorí triedu A
,
pokiaľ na ňu závisí. Príkladom by mohla byť napríklad databáza podpisov,
ktorej inštanciu si vytvorí trieda Faktura
, aby mohla na
vygenerovanej faktúre zobraziť podpis:
Pokiaľ je daná závislosť použitá ešte inde, je výhodnejšie nevytvárať stále nové inštancie závislosti, ale použiť vzor Dependency Injection. Viac v kurze Dependency injection a softvérovej architektúry.
Trieda
B
má dostatok informácií pre inštanciáciu triedy
A
Tu sa jedná o výber takej triedy B
, do ktorej zbytočne
nemusíme naťahovať ďalšie dáta, keď sú už k dispozícii niekde inde.
Ako príklad si uveďme rozhodovanie, či triedu SeznamFaktur
s
faktúrami zákazníka inštanciujeme v triede SpravceFaktur
alebo
SpravceZakazniku
. Pozrieme sa, ktorá z tried má všetky
informácie, ktoré SeznamFaktur
potrebuje. Pokiaľ tu budeme
potrebovať napríklad všetky faktúry az nich vybrať faktúry od určitého
zákazníka, inštanciujeme SeznamFaktur
v
SpravceFaktur
, pretože v ňom sa faktúry nachádzajú:
Trieda B
obsahuje triedu
A
Ak je trieda A
vnorená v triede B
, mala by byť aj
inštancia triedy A
vytváraná triedou B
. Avšak
vnorené triedy sa nestali príliš populárnymi.
UML diagram tejto situácie vyzerá nasledovne:
High cohesion
Vysoká súdržnosť znamená, že sa naša aplikácia skladá z rozumne veľkých kusov kódu. Každý tento kód sa zameriava na jednu vec. To je aj jeden zo základných princípov samotného OOP. Vysoká súdržnosť úzko súvisí s nízkou previazanosťou (pozri ďalej). Keď združujeme súvisiaci kód na jedno miesto, znižuje sa nutnosť väzieb do ďalších častí aplikácie. Ďalším súvisiacim vzorom je Law of Deméter, ktorý v podstate hovorí, že objekt by nemal "hovoriť" s cudzími objektmi.
Príkladom vysokej súdržnosti je napríklad sústredenie funkcionality
okolo užívateľov do triedy SpravceUzivatelu
. Keď by sa
prihlásenie užívateľa riešilo napríklad v triede
SpravceFaktur
, kde je prihlásenie potrebné pre zobrazenie
faktúr, a zrušenie užívateľského účtu by sa riešilo v triede
Uklizec
, ktorá premazáva neaktívne účty, porušovali by sme
High cohesion (viď ďalej). Kód, ktorý má byť spolu v triede
SpravceUzivatelu
, by bol rozhádzaný rôzne po aplikácii, podľa
toho, kde je práve potrebný. Preto združujeme súvisiaci kód na jedno
miesto, a to aj keď sa tieto metódy používajú v aplikácii hoci len
raz.
Indirection
Indirection je veľmi zaujímavý princíp, s ktorým sme sa už stretli pri controlleri. Hovorí, že keď vytvoríme niekde v aplikácii umelého prostredníka, teda triedu „navyše“, môže našu aplikáciu paradoxne výrazne zjednodušiť. U kontroléra jasne vidíme, že zníži počet väzieb medzi objektmi. Za cenu pár riadkov kódu navyše podporíme znovupoužiteľnosť a lepšiu čitateľnosť kódu. Indirection je jeden zo spôsobov, ako dosiahnuť Low coupling (pozri ďalej). Príklad sme si už ukazovali pri vzore Controller.
Information expert
Informačný expert je ďalšia poučka, ktorá nám pomáha rozhodnúť sa do akej triedy pridáme metódu, atribút a podobne. Zodpovednosť má vždy tá trieda, ktorá má najviac informácií. Takej triede potom hovoríme informačný expert a práve do nej pridávame ďalšiu funkcionalitu a dáta. O podobnom princípe sme už hovorili pri vzore Creator v tejto lekcii.
Low coupling
Low coupling je v podstate to isté ako High cohesion, ale z iného pohľadu. V aplikácii by sme mali vytvárať čo najmenší počet väzieb medzi objektmi, čo dosiahneme šikovným rozdelením zodpovednosti.
Ako odstrašujúci príklad si uveďme triedu Manager
, v ktorej
je umiestnená logika pre prácu so zákazníkmi, s faktúrami, s logistikou,
skrátka so všetkým. Takýmto objektom sa niekedy hovorí "božské" (god
objects), pretože majú príliš veľkú zodpovednosť a tým pádom
vytvárajú príliš veľa väzieb (takový Manager
bude typicky
používať veľké množstvo tried, aby mohol fungovať takto všeobecne). Asi
vás už napadlo, že takýto manažér by pravdepodobne nešlo znovu použiť v
inej aplikácii. V aplikácii nie je dôležitý celkový počet väzieb, ale
počet väzieb medzi dvoma objektmi. Vždy sa snažíme, aby trieda komunikovala
s čo najmenším počtom ďalších tried, preto by sme mali uviesť triedy
SpravceUzivatelu
, SpravceFaktur
,
SpravceLogistiky
a podobne.
Tu je diagram UML:
Odstrašujúci príklad božského objektu pri nedodržiavaní Low coupling
A nemusíme zostávať len pri triedach. Low coupling súvisí tiež
napríklad s ďalšími praktikami ohľadom pomenovávania metód. Metódu by
sme mali pomenovávať čo najmenej slovami a bez spojky „a“. Metódy
delej()
alebo naparsujAZpracujAVypis()
signalizujú,
že toho robia príliš.
Keď sme už spomenuli božské objekty, uveďme si aj opačný problém, ktorý je takzvaný Yoyo problém (problém joja). Pri príliš drobnej štruktúre programu, príliš vysokej granularite, často aj nadužívaní dedičnosti, je v programe toľko tried, že programátor sa musí stále prepínať dovnútra nejakej triedy, zistiť ako pracuje a vrátiť sa späť. Táto akcia môže pripomínať vrhanie joja dole a hore, znova a znova. Pred dedičnosťou sa preto často preferuje skladanie objektov.
Čo sa týka väzieb medzi objektmi, mali by sme sa tiež vyvarovať
cyklickým väzbám, ktoré sú všeobecne považované za zlú
praktiku. To sú prípady, keď trieda A
odkazuje na triedu
B
a tá odkazuje späť na triedu A
. Tu je niekde v
návrhu niečo zle. Cyklická väzba môže byť aj napriek viacerým
triedam.
Polymorphism
Áno, aj polymorfizmus je návrhovým vzorom. Aj keď je nám v princípe polymorfizmus dobre známy, zopakujme pre úplnosť, že sa jedná najčastejšie o prípad, keď potomok upravuje funkcionalitu svojho predka, ale zachováva jeho rozhranie.
Z programátorského hľadiska sa jedná o prepisovanie (override) metód
predka. S objektmi potom môžeme pracovať pomocou všeobecného rozhrania, ale
každý objekt si funkcionalitu zdedenú od predka upravuje po svojom.
Polymorfizmus nemusí byť obmedzený len na dedičnosť, ale vo všeobecnosti
na prácu s objektmi rôznych typov pomocou nejakého spoločného rozhrania,
ktoré implementujú. Ukážme si povestný príklad so zvieratami.
Každé zviera má metódu mluv()
, ale prepisuje si ju od predka
Zvire
, aby vydávalo špecifický zvuk:
Ďalšou ukážkou sa ponúka napríklad predok pre formulárové ovládacie
prvky. Každý prvok potom prepisuje metódy predka ako vykresli()
,
vratVelikost()
a podobne podľa toho, ako konkrétne potomkovia
fungujú:
Protected variations
Protected variations by sme mohli preložiť ako chránené zmeny. Praktika hovorí o vytvorení stabilného rozhrania na kľúčových miestach aplikácie, kde by zmena rozhrania spôsobila nutnosť prepísať väčšiu časť aplikácie.
Uveďme si opäť reálny príklad. V systéme ITnetwork používame princíp
Protected variations, konkrétne pomocou návrhového vzoru Adapter.
Tým sa bránime proti zmenám, ktoré neustále vykonáva Facebook vo svojom
API. Prihlasovanie cez Facebook a podobné ďalšie integrácie majú za
následok zvýšenie počtu a aktivity užívateľov, bohužiaľ však za cenu
prepisovania aplikácie každých niekoľko mesiacov. Pomocou rozhrania
FacebookManagerInterface
sa systém už nemusí nikdy meniť. Keď
vyjde nová verzia, kedy Facebook zas všetko prerobí, iba sa toto rozhranie
implementuje v inej triede, napríklad FacebookManagerXX
, kde
XX
je verzia Facebook API. V systéme sa potom zmení inštancia,
ktorá toto rozhranie implementuje. Rozhranie je samozrejme možné definovať
aj pomocou polymorfizmu a abstraktnej triedy:
Pure fabrication
O Pure fabrication sme už dnes tiež hovorili. Voľne preložené ako "čistý výmysel" sa jedná práve o triedy, ktoré slúžia iba pre zjednodušenie systému z hľadiska návrhu. Tak ako bol controller prípad indirection, tak je indirection prípadom Pure fabrication.
Servisné triedy mimo funkcionalitu aplikácie znižujú závislosti a zvyšujú súdržnosť.