Návrhové principy: Tvorba balíčků (2/2) – Závislosti a provázanost

V tomto díle seriálu se podíváme na tři principy používané pro řízení závislostí a provázanosti balíčků (packages).

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

V tomto díle probereme zbývající tři principy pro návrh objektově orientovaných systémů na makro úrovni.  V minulém díle jsme se zabývali principy udržením vnitřní soudržnosti balíčků. V tomto díle se podíváme na vztahy mezi balíčky – jejich vzájemné závislosti.

Závislost (dependency) – v tomto článku budeme tento pojem používat v následujícím významu. Prvek A závisí na prvku B, pokud změna v prvku B vyžaduje provedení změny v prvku A.  Například podtřída je závislá na své bázové třídě, protože změna rozhraní bázové třídy vyžaduje změnu podtřídy.

Uvedené principy vycházejí především z článků, které napsal Robert „Uncle Bob“ C. Martin.

Acyclic Dependencies Principle – ADP

Struktura závislostí mezi balíčky musí tvořit orientovaný acyklický graf.

Jinými slovy v grafu závislostí se nesní objevit žádné smyčky. Nejjednodušším případem smyčky je pokud balíček A závisí na balíčku B, který závisí zpátky na balíčku A. Mohou však existovat i složitější a hůře odhalitelné smyčky: A závisí na B, B závisí na C, C závisí na D, D závisí na A.

Pokud se v závislostech objeví smyčka, vznikne řada problémů. Bude velmi složité určit, jaké balíčky budou ovlivněny provedenou změnou. Dosah změn je mnohem větší a nepředvídatelnější. Sestavení a nasazení aplikace se může kvůli nejasným závislostem stát noční můrou. Smyčky obvykle způsobí, že je potřeba sestavit a nasadit mnoho balíčků, u kterých by to nebylo nutné. Sestavení je složité, protože kvůli smyčkám nejde balíčky kompilovat postupně „zdola-nahoru“ podle jejich závislostí.

Závislosti balíčků jsou způsobeny závislostmi mezi třídami, které obsahují. Jednoduché vzájemné závislosti tříd jeden na jednoho nejsou sice žádoucí, ale ve většině případů jsou přirozené a více méně neškodné. Delší smyčky zahrnující dvě a více tříd však představují potenciální problém. Případné smyčky závislostí tříd bychom se měli snažit udržet tak malé, jak je to jen možné, pokud se jim už nejde rozumným způsobem vyhnout úplně.

Jedním ze základních principů, které bychom zde také měli mít na mysli, je, že vzájemně závislé prvky by neměly být oddělovány do různých balíčků, viz první díl tohoto článku. Vzájemné závislosti tříd začínají být řádově větším problémem, pokud jsou třídy rozděleny do různých balíčků.

V struktuře závislostí balíčků bychom žádné smyčky neměli dovolit.

Balíčky vyskytující se na smyčce se stávají „de facto“ jedním balíčkem – musí být vždy nasazovány všechny a změny jednoho balíčku mají dopad na všechny.

Smyčky v grafu závislostí lze vždy nějakým způsobem odstranit. Existují dva způsoby jak to udělat:

Otočení směru závislosti – Jde o aplikaci Principu obrácení závislostí (Dependency inversion principle). Pokud máme třídu T z balíčku A, na které závisí třídy z balíčku B, vytvoříme abstraktní třídu AT, která je součástí balíčku B. Třídy balíčku B budou závislé na této abstraktní třídě. V balíčku A pak vytvoříme její implementaci. Závislost je tedy obrácená.

Vytvoření nového balíčku – vytvoříme nový balíček, do kterého vydělíme třídy, které mají vzájemné závislosti. Původní balíčky pak budou oba záviset na tomto novém balíčku.

Stable Dependencies Principle – SDP

Důsledky změn se v systému šíří podél vzájemných závislostí. Z pohledů důsledků změn jsou tedy závislosti tranzitivní. Pokud provedeme změnu v balíčku, tato změna může vyžadovat změny v na něm závislých balíčcích, které zase mohou vyžadovat změny v na nich závislých balíčcích atd.

Stabilita – pokud budeme v následujících odstavcích mluvit o stabilitě, budeme mít na mysli to jak obtížné a pravděpodobné je, že se bude balíček měnit. Tedy nikoli zda správně funguje nebo zda způsobuje výpadky. Stabilní balíčky jsou ty balíčky, které se mění obtížně a méně často (mnohdy právě proto, že se mění obtížně).

Pokud velká část systému závisí na nestabilních (často/snadno se měnících) částech kódu, můžeme předpokládat, že systém bude velmi křehký a bude často docházet k chybám. Pokud naopak většina systému závisí na stabilních (pouze těžko a zřídka se měnících) částech kódu, nebudou mít nutné změny takové nepříjemné důsledky.

Z tohoto důvodu je lepší, pokud nestabilní balíčky závisí na těch stabilních než naopak. Měli bychom záviset pouze na věcech, u kterých je nepravděpodobné, že se změní.

Samozřejmě ne všechny části kódu mohou být neměnné – určitá schopnost změny je nutné kvůli rozšiřování a údržbě systému. Jak jsme již probrali u Common Closure Principle – některé balíčky jsou již vytvářeny s citlivostí k určitým typům změn – jsou navrženy tak aby byly v určitých směrech nestabilní. Bez nestabilních částí by systém nebyl flexibilní.

Žádný balíček, o kterém předpokládáme, že se bude měnit (a který je s tímto cílem vytvářen nestabilní), by neměl být cílem závislosti některého stabilního (obtížně měnitelného) balíčku. Jinak dojde k tomu, že provádění změn v nestabilním balíčku bude také obtížné, protože bude vyžadovat i provedení obtížných změn ve stabilním balíčku.

Balíčky by měly záviset pouze na balíčcích, které jsou stabilnější než ony. Čím více věcí na balíčku závisí, tím by měl mít menší a abstraktnější by měl být.

Velice často způsobí provedení jedné změny řetězovou reakci změn v závislých balíčcích. To znesnadňuje odhad náročnosti provedení změn. Dodržování SDP tento efekt omezuje.

Pokud v našem systému máme závislost stabilního balíčku na nestabilním, můžeme se jí zbavit. Vytvoříme nový balíček, který bude obsahovat definice rozhraní tříd nestabilního balíčku. Nestabilní balíček bude pak tvořit implementaci těchto rozhraní a stabilní balíček bude závislý pouze na novém balíčku s rozhraními.

Stable Abstractions Principle – SAP

Tento princip popisuje vztah mezi stabilitou a mírou abstrakce balíčků. Potřebujeme vyvážit dvě potřeby – mít balíčky dostatečně stabilní a zároveň flexibilní. Řešením tohoto rozporu je dodržování Open-closed principle – mít možnost balíčky rozšířit bez potřeby je modifikovat. To je tím jednodušší, čím je balíček abstraktnější.

Stabilní balíčky by měly být tak abstraktní jak je jen možné, aby se zajistilo, že jejich stabilita nebude bránit tomu, aby byly rozšiřovány. Naopak nestabilní balíčky by měly obsahovat konkrétní třídy, jejichž detaily bude snadné změnit.

Míra abstrakce balíčku by měla odpovídat jeho stabilitě. Stabilní balíčky by měly obsahovat abstraktní třídy.

Kombinace principů SDP a SAP tvoří variantu Dependency inversion Principle pro balíčky – závislosti by měly být z konkrétního na abstraktní.

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.

Zdroj: https://www.zdrojak.cz/?p=3766