Inverze závislosti. Kritický pohled na princip inverze závislosti. Tradiční vrstvená architektura

Vlastně všechny zásady PEVNÝÚzce spolu souvisí a jejich hlavním cílem je pomáhat vytvářet kvalitní, škálovatelný software. Ale poslední zásada PEVNÝ na jejich pozadí opravdu vynikají. Nejprve se podívejme na formulaci tohoto principu. Tak, princip inverze závislosti (Princip inverze závislosti – DIP): „Závislost na abstrakcích. Neexistuje žádná závislost na ničem konkrétním.“. Princip zdůrazňuje i známý specialista v oblasti vývoje softwaru Robert Martin DIP a prezentuje to jednoduše jako výsledek dodržování jiných zásad PEVNÝ— princip otevřený/uzavřený a princip Liskovovy substituce. Připomeňme, že první říká, že třída by se neměla upravovat za účelem zavádění nových změn, a druhá se týká dědičnosti a předpokládá bezpečné použití odvozených typů nějakého základního typu, aniž by to zasahovalo do správného chodu programu. Tento princip původně formuloval Robert Martin následujícím způsobem:

1). Moduly na vyšších úrovních by neměly záviset na modulech na nižších úrovních. Moduly na obou úrovních musí záviset na abstrakcích.

2). Abstrakce by neměly záviset na detailech. Podrobnosti musí záviset na abstrakcích.

To znamená, že třídy musí být vyvíjeny z hlediska abstrakcí, a ne jejich konkrétních implementací. A pokud budete dodržovat zásady OCP A LSP, tak to je přesně to, čeho dosáhneme. Vraťme se tedy trochu k lekci. Tam jsme jako příklad považovali třídu Bard, který byl na samém začátku přísně svázán s třídou Kytara, představující konkrétní hudební nástroj:

public class Bard ( private Guitar guitar; public Bard (Gytar guitar) ( this.guitar = kytara; ) public void play() ( guitar.play(); ) )

veřejná třída Bard (

soukromá kytarová kytara;

public Bard (kytara)

tento. kytara = kytara ;

public void play()

kytara hrát si();

Pokud bychom chtěli do této třídy přidat podporu pro další hudební nástroje, museli bychom tuto třídu tak či onak upravit. To je jasné porušení zásady OCP. A možná jste si již všimli, že jde také o porušení zásady DIP, protože v našem případě se ukázalo, že naše abstrakce je závislá na detailech. Z hlediska dalšího rozšiřování naší třídy to není vůbec dobré. Aby naše třída splňovala podmínky zásady OCP do systému jsme přidali rozhraní Nástroj, kterou realizovaly specifické třídy představující určité typy hudebních nástrojů.

Soubor Instrument.java:

nástroj veřejného rozhraní ( void play(); )

nástroj veřejného rozhraní (

void play();

Soubor Guitar.java:

class Guitar implements Instrument( @Override public void play() ( System.out.println("Play Guitar!"); ) )

třída Kytarové nářadí Nástroj (

@Přepsat

public void play()

Systém. ven . println("Hraj na kytaru!");

Soubor Lute.java:

public class Lute implementuje Instrument( @Override public void play() ( System.out.println("Play Lute!"); ) )

public class Lute implementuje Instrument (

@Přepsat

public void play()

Systém. ven . println("Hraj na loutnu!");

Poté jsme změnili třídu Bard, abychom v případě potřeby mohli nahradit implementace přesně těmi, které potřebujeme. To vnáší do vytvořeného systému další flexibilitu a snižuje jeho vazbu (silné závislosti tříd na sobě).

public class Bard ( private Instrument instrument; public Bard() ( ) public void play() ( instrument.play(); ) public void setInstrument(Instrument tool) ( this.instrument = instrument; ) )

veřejná třída Bard (

soukromý nástroj nástroj ;

Inverze závislostí je jedním z nejdůležitějších programovacích idiomů. Na ruskojazyčném internetu je překvapivě málo popisů tohoto idiomu (principu). Tak jsem se rozhodl, že zkusím udělat popis. Udělám příklady v Javě, in tento moment Je to pro mě jednodušší, i když princip inverze závislostí platí pro jakýkoli programovací jazyk.

Tento popis byl vyvinut společně s Vladimirem Matveevem v rámci přípravy na hodiny se studenty studujícími Javu.

Další články z této série:

Dovolte mi začít definicí „závislosti“. co je závislost? Pokud váš kód používá nějakou třídu interně nebo explicitně volá statickou metodu nějaké třídy nebo funkce, jedná se o závislost. Dovolte mi to vysvětlit na příkladech:

Níže třída A uvnitř metody zvané someMethod() explicitně vytvoří objekt třídy B a zavolá jeho metodu someMethodOfB()

Veřejná třída A ( void someMethod() ( B b = new B(); b.someMethodOfB(); ) )

Podobně například třída B explicitně přistupuje ke statickým polím a metodám třídy System:

Veřejná třída B ( void someMethodOfB() ( System.out.println("Ahoj světe"); ) )

Ve všech případech, kdy jakákoli třída (typ A) nezávisle vytvoří jakoukoli třídu (typ B) nebo explicitně přistupuje ke statickým polím nebo členům třídy, nazývá se to rovný závislost. Tito. důležité: pokud třída uvnitř sebe funguje s jinou třídou, jedná se o závislost. Pokud také vytvoří tuto třídu uvnitř sebe, pak toto rovný závislost.

Co je špatného na přímých závislostech? Přímé závislosti jsou špatné, protože třída, která v sobě nezávisle vytváří další třídu, je „pevně“ svázána s touto třídou. Tito. pokud je výslovně napsáno, že B = new B(); , pak bude třída A vždy pracovat s třídou B a žádnou jinou třídou. Nebo pokud to říká System.out.println("..."); pak se třída vždy zobrazí na System.out a nikde jinde.

Pro malé třídy nejsou závislosti nijak hrozné. Tento kód může fungovat docela dobře. Ale v některých případech, aby vaše třída A mohla fungovat univerzálně v prostředí různých tříd, může vyžadovat další implementace tříd – závislosti. Tito. Například nebudete potřebovat třídu B, ale jinou třídu se stejným rozhraním nebo ne System.out, ale například výstup do loggeru (například log4j).

Přímý vztah lze graficky zobrazit následovně:

Tito. když ve svém kódu vytvoříte třídu A: A a = new A(); ve skutečnosti nevzniká jen jedna třída A, ale celá hierarchie závislých tříd, jejíž příklad je na obrázku níže. Tato hierarchie je „rigidní“: bez změny zdrojového kódu jednotlivých tříd nemůžete nahradit žádnou z tříd v hierarchii. Proto je třída A v takové implementaci špatně adaptabilní na měnící se prostředí. S největší pravděpodobností jej nelze použít v žádném jiném kódu, než je konkrétní kód, pro který jste jej napsali.

Chcete-li oddělit třídu A od konkrétních závislostí, použijte injekce závislosti. Co je injekce závislosti? Namísto explicitního vytvoření požadované třídy v kódu jsou závislosti předány do třídy A prostřednictvím konstruktoru:

Public class A ( private final B b; public A(B b) ( this.b = b; ) public void someMethod() ( b.someMethodOfB(); ) )

Že. třída A nyní přijímá svou závislost prostřednictvím konstruktoru. Nyní, abyste vytvořili třídu A, musíte nejprve vytvořit její závislou třídu. V v tomto případě toto je B:

B b = nové B(); A a = nové A(b); a.someMetoda();

Pokud se stejný postup opakuje pro všechny třídy, tzn. předejte instanci třídy D konstruktoru třídy B, její závislosti E a F konstruktoru třídy D atd., pak získáte kód, ve kterém jsou všechny závislosti vytvořeny v obráceném pořadí:

Gg = nový G(); Hh = nové H(); Ff = nové (g,h); E e = nové E(); D d = nové D(e,f); Bb = nové B(d); A a = nové A(b); a.someMetoda();

To lze graficky znázornit takto:

Pokud porovnáte 2 obrázky - obrázek výše s přímými závislostmi a druhý obrázek s injekcí závislostí - můžete vidět, že směr šipek se změnil na opačný. Z tohoto důvodu se idiom nazývá „inverze“ závislostí. Jinými slovy, inverze závislostí znamená, že třída sama o sobě nevytváří závislosti, ale přijímá je ve vytvořené podobě v konstruktoru (nebo jinak).

Proč je inverze závislosti dobrá? Pomocí inverze závislostí můžete nahradit všechny závislosti ve třídě, aniž byste změnili její kód. To znamená, že vaši třídu A lze flexibilně nakonfigurovat pro použití v jiném programu, než pro který byla původně napsána. Že. Princip inverze závislostí (někdy nazývaný princip vkládání závislostí) je klíčový pro vytvoření flexibilního, modulárního, opakovaně použitelného kódu.

Nevýhoda vkládání závislostí je také viditelná na první pohled - objekty tříd navržených pomocí tohoto vzoru jsou pracné na konstrukci. Proto se vkládání závislostí obvykle používá ve spojení s nějakou knihovnou navrženou pro usnadnění tohoto úkolu. Například jedna z knihoven Google Guice. Cm. .

2 odpovědi

Dobrá otázka - slovo inverze je poněkud překvapivé (vzhledem k tomu, že po použití DIP modul závislosti na nižší úrovni zjevně nyní více nezávisí na modulu volajícího vysoká úroveň: buď volající, nebo závislý jsou nyní volněji spojeny prostřednictvím další abstrakce).

Někdo by se mohl zeptat, proč používám slovo „inverze“. Abychom byli spravedliví, je to proto, že tradičnější metody vývoje softwaru, jako je strukturovaná analýza a návrh, mají tendenci vytvářet softwarové struktury, ve kterých moduly na vysoké úrovni závisí na modulech nízké úrovně a ve kterých abstrakce závisí na detailech. Ve skutečnosti je jedním z účelů těchto metod definovat hierarchii podprogramů, která popisuje, jak moduly na vysoké úrovni volají moduly na nízké úrovni.... Struktura závislostí dobře navrženého objektově orientovaného programu je tedy " převrácený" vzhledem ke struktuře závislosti, která je typicky výsledkem tradičních procedurálních metod.

Při čtení článku strýčka Boba o DIP je třeba poznamenat, že C++ nemá (a v době psaní tohoto článku nemá) rozhraní, takže dosažení této abstrakce v C++ je obvykle implementováno prostřednictvím abstraktní/čisté virtuální základní třídy, zatímco v Java nebo C# abstrakce pro uvolnění spojení se obvykle oddělí abstrahováním rozhraní od závislosti a připojením modulu(ů) vyšší úrovně k rozhraní.

Upravit Jen pro objasnění:

"Někde to také vidím nazvané inverze závislosti"

Inverze: Inverze správy závislostí z aplikace do kontejneru (např. Spring).

Injekce závislosti:

Místo psaní tovární šablony, co takhle vložit objekt přímo do třídy klienta. Nechte tedy třídu klienta odkazovat na rozhraní a měli bychom být schopni vložit konkrétní typ do třídy klienta. Díky tomu třída klienta nemusí používat nové klíčové slovo a je zcela oddělena od konkrétních tříd.

A co Inversion of Control (IoC)?

V tradičním programování je tok obchodní logiky definován objekty, které jsou k sobě staticky přiřazeny. S inverzí řízení tok závisí na objektovém grafu, který je vytvořen instancí assembleru a umožněný objektovými interakcemi definovanými prostřednictvím abstrakcí. Procesu vazby je dosaženo prostřednictvím vkládání závislostí, ačkoli někteří tvrdí, že použití lokátoru služeb také poskytuje inverzi kontroly.

Inverze řízení jako průvodce designem slouží k následujícím účelům:

  • Dochází k oddělení provedení konkrétního úkolu od jeho realizace.
  • Každý modul se může zaměřit na to, k čemu má sloužit.
  • Moduly nepředpokládají, co dělají ostatní systémy, ale spoléhají na své smlouvy.
  • Výměna modulů nemá vliv na ostatní moduly.

Pro získání dodatečné informace Koukni se.

14 odpovědí

V zásadě se říká:

  • Abstrakce by nikdy neměly záviset na detailech. Podrobnosti musí záviset na abstrakcích.

Pokud jde o to, proč je to důležité, jedním slovem: změna je riskantní a v závislosti na koncepci, spíše než na implementaci, snížíte potřebu změn na stránkách volání.

DIP účinně snižuje vazbu mezi různými částmi kódu. Myšlenka je taková, že i když existuje mnoho způsobů, jak implementovat, řekněme, logger, způsob, jakým jej používáte, by měl být v průběhu času relativně stabilní. Pokud můžete extrahovat rozhraní, které představuje koncept protokolování, toto rozhraní by mělo být v průběhu času mnohem stabilnější než jeho implementace a volající weby by měly být mnohem méně náchylné ke změnám, které můžete provést údržbou nebo rozšířením tohoto mechanismu protokolování.

Protože implementace je specifická pro rozhraní, máte možnost si za běhu vybrat, která implementace je pro vaše konkrétní prostředí nejvhodnější. V závislosti na případu to může být také zajímavé.

Knihy Agile Software Development, Principles, Patterns and Practices a Agile Principles, Patterns and Practices in C# jsou nejlepšími zdroji pro plné pochopení původních cílů a motivací stojících za Principem Inversion Inversion. Článek „Princip obrácení závislosti“ je také dobrým zdrojem, ale vzhledem k tomu, že se jedná o zhuštěnou verzi návrhu, který se nakonec dostal do výše zmíněných knih, zanechává za sebou některé důležité diskuse o konceptu vlastnictví balíku a rozhraní, která jsou pro ně klíčová Tento princip se liší od obecnější rady „program pro rozhraní, nikoli implementaci“, kterou najdete v knize Design Patterns (Gamma a kol.).

Pro stručné shrnutí, princip inverze závislostí primárně směřuje k změna tradičně směřování závislostí z komponent „vyšší úrovně“ na komponenty „nižší úrovně“, takže komponenty „nižší úrovně“ závisejí na rozhraních, patřící ke komponentám „vyšší úrovně". (Poznámka: Komponenta „vyšší úrovně" zde odkazuje na komponentu vyžadující externí závislosti/služby, a nikoli nutně na její koncepční pozici ve vrstvené architektuře.) Vztah však není klesá stejně jako ona směny od komponent, které jsou teoreticky méně hodnotné, po komponenty, které jsou teoreticky hodnotnější.

Toho je dosaženo navržením komponent, jejichž externí závislosti jsou vyjádřeny jako rozhraní, pro které musí spotřebitel komponenty poskytnout implementaci. Jinými slovy, určitá rozhraní vyjadřují to, co komponenta potřebuje, nikoli to, jak komponentu používáte (například „INeedSomething“ spíše než „IDoSomething“).

Princip inverze závislostí neřeší jednoduchý postup abstrahování závislostí pomocí rozhraní (např. MyService -> ). Ačkoli to odděluje komponentu od konkrétního detailu implementace závislosti, neinvertuje to vztah mezi spotřebitelem a závislostí (například ⇐ Logger.

Význam principu inverze závislostí lze zredukovat na jediný cíl – schopnost opakovaně používat softwarové komponenty, které se pro část své funkčnosti (registrace, ověřování atd.) spoléhají na externí závislosti.

V rámci tohoto obecného cíle opětovného použití můžeme rozlišit dva podtypy opětovného použití:

    Používání softwarové komponenty ve více aplikacích s implementacemi závislostí (například jste vyvinuli DI kontejner a chcete poskytovat protokolování, ale nechcete svůj kontejner vázat na konkrétní protokolovací zařízení, takže každý, kdo používá váš kontejner, musí také používat protokolování knihovna, kterou si vyberete).

    Používání softwarových komponent ve vyvíjejícím se kontextu (například jste vyvinuli komponenty obchodní logiky, které zůstávají stejné v různých verzích aplikace, kde se vyvíjejí detaily implementace).

V prvním případě opakovaného použití komponent ve více aplikacích, jako je například knihovna infrastruktury, je cílem poskytnout zákazníkům základní infrastrukturu, aniž by byli spojeni se závislostmi vaší vlastní knihovny, protože získání závislostí z takových závislostí vyžaduje, aby spotřebitelé také vyžadovali stejné závislosti. To může být problematické, když se spotřebitelé vaší knihovny rozhodnou použít jinou knihovnu pro stejné potřeby infrastruktury (jako je NLog a log4net), nebo když se rozhodnou použít pozdější verzi požadované knihovny, která není zpětně kompatibilní s požadovanou verzí. ve vaší knihovně.

V druhém případě opětovného použití komponent obchodní logiky (tj. „komponenty vyšší úrovně“) je cílem izolovat implementaci aplikace v hlavním rozsahu od měnících se potřeb podrobností vaší implementace (např. změna/aktualizace trvalých knihoven, zprávy výměnných knihoven) . šifrovací strategie atd.). V ideálním případě by změna podrobností implementace aplikace neměla narušit komponenty, které zapouzdřují obchodní logiku aplikace.

Poznámka. Někteří mohou mít námitky proti popisu tohoto druhého případu jako skutečného opětovného použití, protože se domnívají, že komponenty, jako jsou komponenty obchodní logiky použité v jedné vývojové aplikaci, představují pouze jedno použití. Myšlenka je zde však taková, že každá změna v detailech implementace aplikace představuje nový kontext, a tedy jiný případ použití, i když konečné cíle lze rozlišit jako izolaci a přenositelnost.

I když ve druhém případě může mít dodržování principu inverze závislostí určité výhody, je třeba poznamenat, že jeho význam při aplikaci na moderní jazyky, jako je Java a C#, byl značně snížen, možná do té míry, že je irelevantní. Jak bylo uvedeno výše, DIP zahrnuje úplné oddělení implementačních detailů do samostatných balíčků. V případě vyvíjející se aplikace však pouhé použití rozhraní definovaných v podmínkách obchodní domény ochrání před potřebou modifikovat komponenty vyšší úrovně kvůli měnícím se potřebám komponent detailů implementace, a to i v případě, že podrobnosti implementace jsou nakonec ve stejném balíčku. . Tato část principu odráží aspekty, které byly relevantní pro jazyk v době jeho kodifikace (například C++), které nejsou relevantní pro novější jazyky. Důležitost Principu inverze závislostí však primárně souvisí s vývojem opakovaně použitelných softwarových komponent/knihoven.

Podrobnější diskuse o tomto principu, jak se týká snadné použití lze nalézt rozhraní, vkládání závislostí a vzor rozděleného rozhraní.

Když vyvíjíme softwarové aplikace, můžeme zvážit třídy nízké úrovně – třídy, které implementují základní a primární operace (přístup k disku, síťové protokoly atd.) a třídy vysoké úrovně – třídy, které zapouzdřují komplexní logiku (obchodní toky,... ) .

Ty druhé spoléhají na třídy nízké úrovně. Přirozeným způsobem implementace takových struktur by bylo psát nízkoúrovňové třídy a kdykoli jsme nuceni psát složité třídy na vysoké úrovni. Protože třídy na vysoké úrovni jsou definovány z pohledu ostatních, zdá se, že je to logický způsob, jak toho dosáhnout. To ale není responzivní design. Co se stane, když potřebujeme nahradit třídu nízké úrovně?

Princip inverze závislosti říká, že:

  • Moduly vysoké úrovně by neměly záviset na modulech nízké úrovně. Obojí musí záviset na abstrakcích.

Tento princip si klade za cíl „převrátit“ obvyklou myšlenku, kterou obsahují moduly na vysoké úrovni software musí záviset na modulech nižší úrovně. Zde moduly na vysoké úrovni vlastní abstrakci (například metody rozhodování o rozhraní), které jsou implementovány moduly nižší úrovně. Moduly nižší úrovně tedy závisí na modulech vyšší úrovně.

Efektivní využití inverze závislostí poskytuje flexibilitu a stabilitu v celé architektuře vaší aplikace. To umožní vaší aplikaci vyvíjet se bezpečněji a stabilněji.

Tradiční vrstvená architektura

Uživatelské rozhraní vrstvené architektury tradičně záviselo na obchodní vrstvě, která zase závisela na vrstvě přístupu k datům.

Musíte rozumět vrstvě, balíčku nebo knihovně. Podívejme se, jak jde kód.

Měli bychom knihovnu nebo balíček pro vrstvu přístupu k datům.

// DataAccessLayer.dll veřejná třída ProductDAO ( )

// BusinessLogicLayer.dll pomocí DataAccessLayer; public class ProductBO ( private ProductDAO productDAO; )

Vrstvená architektura s inverzí závislostí

Inverze závislosti indikuje následující:

Moduly vysoké úrovně by neměly záviset na modulech nízké úrovně. Obojí musí záviset na abstrakcích.

Abstrakce by neměly záviset na detailech. Podrobnosti musí záviset na abstrakcích.

Co jsou moduly vysoké a nízké úrovně? Když přemýšlíme o modulech, jako jsou knihovny nebo balíčky, budou vysokoúrovňové moduly ty, které mají tradičně závislosti, a nízkoúrovňové, na kterých jsou závislé.

Jinými slovy, vysoká úroveň modulu bude tam, kde se akce volá, a nízká úroveň, kde se akce provede.

Z tohoto principu lze vyvodit rozumný závěr: mezi uzly by neměla být žádná závislost, ale měla by existovat závislost na abstrakci. Ale podle přístupu, který volíme, možná používáme investiční závislost nesprávně, ale to je abstrakce.

Představte si, že přizpůsobíme náš kód takto:

Měli bychom knihovnu nebo balíček pro vrstvu přístupu k datům, která definuje abstrakci.

// DataAccessLayer.dll veřejné rozhraní IProductDAO veřejná třída ProductDAO: IProductDAO( )

A další obchodní logika na úrovni knihovny nebo balíčku, která závisí na vrstvě přístupu k datům.

// BusinessLogicLayer.dll pomocí DataAccessLayer; public class ProductBO (soukromý produkt IProductDAO productDAO; )

Přestože jsme závislí na abstrakci, vztah mezi obchodem a přístupem k datům zůstává stejný.

K dosažení inverze závislosti musí být rozhraní persistence definováno v modulu nebo balíčku, kde je umístěna logika nebo doména vysoké úrovně, nikoli v modulu nízké úrovně.

Nejprve definujte, co je doménová vrstva a abstrakce její komunikace je definována perzistencí.

// Domain.dll veřejné rozhraní IProductRepository; pomocí DataAccessLayer; public class ProductBO ( private IProductRepository productRepository; )

Jakmile je úroveň perzistence závislá na doméně, je nyní možné invertovat, pokud je definována závislost.

// Persistence.dll veřejná třída ProductDAO: IProductRepository()

Prohloubení principu

Je důležité dobře porozumět konceptu, prohloubit účel a výhody. Pokud zůstaneme v mechanice a budeme studovat typický repozitář, nebudeme schopni určit, kde můžeme uplatnit princip závislosti.

Ale proč převracíme závislost? Co je hlavním cílem nad rámec konkrétních příkladů?

To je obvykle umožňuje, aby se ty nejstabilnější věci, které jsou nezávislé na věcech méně stabilních, častěji měnily.

Je snazší změnit typ persistence nebo databázi nebo technologii pro přístup ke stejné databázi než doménovou logiku nebo akce navržené pro komunikaci s persistencí. To způsobí, že závislost bude obrácena, protože je snazší změnit trvalost, pokud k této změně dojde. Nebudeme tak muset měnit doménu. Doménová vrstva je nejstabilnější ze všech, takže by neměla na ničem záviset.

Ale existuje více než jen tento příklad úložiště. Existuje mnoho scénářů, ve kterých je tento princip aplikován, a existují architektury založené na tomto principu.

architektura

Existují architektury, ve kterých je inverze závislostí klíčem k její definici. Ve všech doménách je to nejdůležitější a právě abstrakce budou specifikovat komunikační protokol mezi doménou a zbytkem balíčků nebo knihoven.

Čistá architektura

Pro mě princip inverze závislostí popsaný v oficiálním článku

Problém v C++ je, že hlavičkové soubory obvykle obsahují deklarace soukromých polí a metod. Pokud tedy modul C++ na vysoké úrovni obsahuje hlavičkový soubor pro modul nízké úrovně, bude to záviset na skutečnosti implementace podrobnosti o tomto modulu. A to evidentně není moc dobré. Ale to není problém v modernějších jazycích, které se dnes běžně používají.

Moduly na vysoké úrovni jsou ze své podstaty méně znovu použitelné než moduly nízké úrovně, protože moduly nižší úrovně jsou obvykle více specifické pro aplikaci/kontext než moduly nižší úrovně. Například komponenta, která implementuje obrazovku uživatelského rozhraní, je nejvyšší úrovně a také velmi (zcela?) specifická pro aplikaci. Pokus o opětovné použití takové součásti v jiné aplikaci je kontraproduktivní a může vést pouze k nadměrnému vývoji.

Vytvoření samostatné abstrakce na stejné úrovni komponenty A, která závisí na komponentě B (která nezávisí na A), lze tedy provést pouze v případě, že komponenta A bude skutečně užitečná pro opětovné použití v různých aplikacích nebo kontextech. Pokud tomu tak není, pak bude aplikace DIP špatným návrhem.

Jasnější způsob, jak uvést princip inverze závislosti:

Vaše moduly, které zapouzdřují komplexní obchodní logiku, by neměly přímo záviset na jiných modulech, které zapouzdřují obchodní logiku. Místo toho by měly záviset pouze na rozhraních k jednoduchým datům.

To znamená, že místo implementace vaší třídy Logic, jak to lidé obvykle dělají:

Class Dependency ( ... ) class Logic ( private Dependency dep; int doSomething() ( // Obchodní logika pomocí dep zde ) )

měli byste udělat něco jako:

Class Dependency ( ... ) rozhraní Data ( ... ) třída DataFromDependency implementuje Data ( private Dependency dep; ... ) class Logic ( int doSomething(Data data) ( // vypočítat něco s daty ) )

Data a DataFromDependency by měly žít ve stejném modulu jako Logic, nikoli se závislostí.

Proč je to?

Dobré odpovědi a dobré příklady již zde dali jiní.

Smyslem inverze závislosti je umožnit opětovné použití softwaru.

Myšlenka je taková, že místo toho, aby se dva kusy kódu spoléhaly na sebe, spoléhají na nějaké abstraktní rozhraní. Poté můžete znovu použít jakoukoli část bez druhé.

Toho je obvykle dosaženo invertováním řídicího kontejneru (IoC), jako je Spring v Javě. V tomto modelu se vlastnosti objektů nastavují pomocí konfigurace XML, spíše než objekty vycházejí a hledají svou závislost.

Představte si tento pseudokód...

Veřejná třída MyClass ( public Service myService = ServiceLocator.service; )

MyClass přímo závisí jak na třídě Service, tak na třídě ServiceLocator. To je vyžadováno pro oba, pokud jej chcete použít v jiné aplikaci. A teď si představ tohle...

Veřejná třída MyClass (veřejná IService myService; )

MyClass nyní používá jedno rozhraní, rozhraní IService. Nechali bychom kontejner IoC skutečně nastavit hodnotu této proměnné.

Ať existuje hotel, který žádá výrobce potravin o své zásoby. Hotel zadá název jídla (řekněme kuře) Generátoru jídla a Generátor vrátí požadované jídlo do hotelu. Ale hotel se nestará o typ jídla, které dostává a podává. Generátor tak dodává do hotelu jídlo označené jako „Jídlo“.

Tato implementace v JAVA

FactoryClass s tovární metodou. Generátor jídla

Public class FoodGenerator ( Food food; public Food getFood(String name)( if(name.equals("fish"))( food = new Fish(); )else if(name.equals("kuře"))( food = nové kuře(); )ostatní jídlo = null; vrátit jídlo; ) )

Anotace/rozhraní třídy

Veřejná abstraktní třída Jídlo ( //Žádný z podřízené třídy nepřepíše tuto metodu, aby byla zajištěna kvalita... public void quality())( String fresh = "Toto je čerstvé " + getName(); String tasty = "Toto je chutné " + getName(); System.out.println(fresh); System.out.println(tasty); ) public abstract String getName(); )

Kuře prodává jídlo (třída betonu)

Public class Chicken rozšiřuje Food ( /*Všechny druhy potravin musí být čerstvé a chutné, takže * Nepřepisují metodu super třídy "property()"*/ public String getName())( return "Kuře"; ))

Ryby prodávají jídlo (specifická třída)

Public class Fish rozšiřuje Food ( /*Všechny typy potravin musí být čerstvé a chutné, takže * Nepřepisují metodu super třídy "property()"*/ public String getName())( return "Fish"; ))

Konečně

Hotel

Public class Hotel ( public static void main(String args)( //Použití třídy Factory.... FoodGenerator foodGenerator = new FoodGenerator(); //Tovární metoda pro vytvoření instance potravin... Food food = foodGenerator.getFood( "kuře"); food.quality(); ) )

Jak vidíte, hotel neví, jestli je to kuře nebo ryba. Ví se pouze, že se jedná o potravinu, tzn. Hotel závisí na třídě jídla.

Můžete si také všimnout, že třída Ryby a kuře implementuje třídu Jídlo a není přímo spojena s hotelem. těch. kuře a ryby také závisí na potravinové třídě.

To znamená, že složka vysoké úrovně (hotel) a složka nízké úrovně (ryby a kuře) závisí na abstrakci (jídlo).

Tomu se říká inverze závislostí.

Uvádí to Princip inverze závislosti (DIP).

i) Vysokoúrovňové moduly by neměly záviset na nízkoúrovňových modulech. Obojí musí záviset na abstrakcích.

ii) Abstrakce by nikdy neměly záviset na detailech. Podrobnosti musí záviset na abstrakcích.

Veřejné rozhraní ICustomer ( řetězec GetCustomerNameById(int id); ) veřejná třída Zákazník: ICustomer ( //ctor public Customer()() veřejný řetězec GetCustomerNameById(int id) ( return "Dummy Customer Name"; ) ) veřejná třída CustomerFactory ( public static ICustomer GetCustomerData() ( return new Customer(); ) ) public class CustomerBLL ( ICustomer _customer; public CustomerBLL() ( _customer = CustomerFactory.GetCustomerData(); ) public string GetCustomerNameById(int id) ( return _customer); ) ) public class Program ( static void Main() ( CustomerBLL customerBLL = new CustomerBLL(); int customerId = 25; řetězec customerName = customerBLL.GetCustomerNameById(customerId); Console.WriteLine(customerName); Console.ReadKey(); ) )

Poznámka. Třída by měla záviset na abstrakcích, jako je rozhraní nebo abstraktní třídy, spíše než na konkrétních detailech (implementace rozhraní).

podíl

Poslední aktualizace: 03/11/2016

Princip inverze závislosti Princip inverze závislosti se používá k vytvoření volně spojených entit, které lze snadno testovat, upravovat a aktualizovat. Tento princip lze formulovat následovně:

Moduly nejvyšší úrovně by neměly záviset na modulech nižší úrovně. Obojí musí záviset na abstrakcích.

Abstrakce by neměly záviset na detailech. Podrobnosti musí záviset na abstrakcích.

Chcete-li pochopit princip, zvažte následující příklad:

Class Book ( public string Text ( get; set; ) public ConsolePrinter Printer ( get; set; ) public void Print() ( Printer.Print(Text); ) ) class ConsolePrinter ( public void Print(text string) ( Console.WriteLine (text); ) )

Třída Book, která představuje knihu, používá k tisku třídu ConsolePrinter. S touto definicí závisí třída Book na třídě ConsolePrinter. Navíc jsme striktně definovali, že tisk knihy lze provést pouze na konzole pomocí třídy ConsolePrinter. Další možnosti, například výstup na tiskárnu, výstup do souboru nebo použití některých prvků grafického rozhraní – to vše je v tomto případě vyloučeno. Knihtisková abstrakce není oddělena od detailů třídy ConsolePrinter. To vše je porušení principu inverze závislosti.

Nyní se pokusme uvést naše třídy do souladu s principem inverze závislostí a oddělit abstrakce od nízkoúrovňové implementace:

Rozhraní IPrinter ( void Print(string text); ) class Book ( public string Text ( get; set; ) public IPrinter Printer ( get; set; ) public Book(IPrinter printer) ( this.Printer = printer; ) public void Print( ) ( Printer.Print(Text); ) ) class ConsolePrinter: IPrinter ( public void Print(řetězcový text) ( Console.WriteLine("Print to console"); ) ) class HtmlPrinter: IPrinter ( public void Print(řetězcový text) ( Console.WriteLine("Tisk v html"); ))

Knihtisková abstrakce je nyní oddělena od konkrétních realizací. Výsledkem je, že jak třída Book, tak třída ConsolePrinter závisí na abstrakci IPrinter. Kromě toho nyní můžeme také vytvářet další nízkoúrovňové implementace abstrakce IPrinter a dynamicky je aplikovat v programu:

Kniha kniha = new Book(new ConsolePrinter()); kniha.Tisk(); book.Printer = new HtmlPrinter(); kniha.Tisk();