Afhængighedsinversionsprincippet implementeres vha. Afhængighedsinversion

hjem / skænderi

Faktisk alle principperne SOLID De er tæt knyttet til hinanden, og deres hovedmål er at hjælpe med at skabe skalerbar software af høj kvalitet. Men det sidste princip SOLID skiller sig virkelig ud på deres baggrund. Lad os først se på formuleringen af ​​dette princip. Så, afhængighedsinversionsprincip (Dependency Inversion Principle - DIP): "Afhængighed af abstraktioner. Der er ingen afhængighed af noget bestemt.". Den kendte specialist inden for softwareudvikling, Robert Martin, lægger også vægt på princippet DIP og præsenterer det blot som et resultat af at følge andre principper SOLID— åbent/lukket-princippet og Liskov-substitutionsprincippet. Husk på, at den første siger, at klassen ikke skal ændres for at indføre nye ændringer, og den anden vedrører nedarvning og forudsætter sikker brug af afledte typer af en eller anden basistype uden at forstyrre den korrekte drift af programmet. Robert Martin formulerede oprindeligt dette princip som følger:

1). Moduler på højere niveauer bør ikke afhænge af moduler på lavere niveauer. Moduler på begge niveauer skal afhænge af abstraktioner.

2). Abstraktioner bør ikke afhænge af detaljer. Detaljer må afhænge af abstraktioner.

Det vil sige, klasser skal udvikles i form af abstraktioner, og ikke deres konkrete implementeringer. Og hvis du følger principperne OCP Og LSP, så er det præcis, hvad vi vil opnå. Så lad os gå lidt tilbage til lektionen om. Der betragtede vi som eksempel klassen Bard, som i begyndelsen var strengt bundet til klassen Guitar, der repræsenterer et specifikt musikinstrument:

offentlig klasse Bard ( privat guitarguitar; offentlig Bard(guitarguitar) ( this.guitar = guitar; ) public void play() ( guitar.play(); ) )

offentlig klasse Bard (

privat Guitar guitar;

public Bard (guitar guitar)

det her. guitar = guitar ;

public void play()

guitar Spil();

Hvis vi ville tilføje støtte til andre musikinstrumenter til denne klasse, ville vi være nødt til at ændre denne klasse på den ene eller anden måde. Dette er en klar overtrædelse af princippet OCP. Og du har måske allerede bemærket, at det også er brud på princippet DIP, da vores abstraktion i vores tilfælde viste sig at være afhængig af detaljer. Ud fra et synspunkt om yderligere udvidelse af vores klasse, er dette slet ikke godt. Så vores klasse opfylder princippets betingelser OCP vi har tilføjet en grænseflade til systemet Instrument, som blev implementeret af specifikke klasser, der repræsenterer visse typer musikinstrumenter.

Fil Instrument.java:

offentlig grænseflade Instrument ( ugyldig afspilning(); )

offentlig grænseflade Instrument (

ugyldig afspilning();

Fil Guitar.java:

klasse Guitar implementerer Instrument( @Override public void play() ( System.out.println("Spil guitar!"); ) )

klasse guitarredskaber Instrument (

@Tilsidesæt

public void play()

System. ud. println("Spil guitar!");

Fil Lute.java:

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

offentlig klasse Lute redskaber Instrument (

@Tilsidesæt

public void play()

System. ud. println("Spil lut!");

Derefter skiftede vi klasse Bard, så vi om nødvendigt kan udskifte implementeringer med præcis dem, vi skal bruge. Dette introducerer yderligere fleksibilitet i det oprettede system og reducerer dets kobling (stærk afhængighed af klasser af hinanden).

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

offentlig klasse Bard (

privat instrument instrument ;

2 svar

Godt spørgsmål - ordet inversion er noget overraskende (da efter at have anvendt DIP, afhænger afhængighedsmodulet på lavere niveau åbenbart ikke nu af opkaldsmodulet på højere niveau: enten kalderen eller den afhængige er nu mere løst koblet gennem en ekstra abstraktion ).

Man kan spørge, hvorfor jeg bruger ordet "inversion". For at være retfærdig skyldes det, at mere traditionelle softwareudviklingsmetoder, såsom struktureret analyse og design, har tendens til at skabe softwarestrukturer, hvor højniveaumoduler afhænger af lavniveaumoduler, og hvor abstraktioner afhænger af detaljer. Faktisk er et af formålene med disse metoder at definere et subrutinehierarki, der beskriver, hvordan moduler på højt niveau foretager opkald til moduler på lavt niveau.... Således er afhængighedsstrukturen for et veldesignet objektorienteret program " inverteret" i forhold til den afhængighedsstruktur, der typisk er resultatet af traditionelle proceduremæssige metoder.

En pointe at bemærke, når man læser onkel Bobs papir om DIP er, at C++ ikke (og i skrivende stund ikke har) grænseflader, så opnåelse af denne abstraktion i C++ implementeres normalt via en abstrakt/ren virtuel basisklasse, hvorimod i Java eller C# abstraktionen til at løsne koblingen er normalt afkobling ved at abstrahere grænsefladen fra afhængigheden og koble modulet/modulerne på højere niveau til grænsefladen.

Redigere Bare for at præcisere:

"Et sted ser jeg det også kaldet afhængighedsinversion"

Inversion: Invertering af afhængighedsstyring fra en applikation til en container (f.eks. Spring).

Afhængighedsindsprøjtning:

I stedet for at skrive en fabriksskabelon, hvad med at injicere et objekt direkte i klientklassen. Så lad klientklassen henvise til grænsefladen, og vi burde være i stand til at injicere en konkret type i klientklassen. Hermed behøver klientklassen ikke at bruge et nyt søgeord og er helt adskilt fra de konkrete klasser.

Hvad med Inversion of Control (IoC)?

I traditionel programmering er strømmen af ​​forretningslogik defineret af objekter, der er statisk tildelt hinanden. Med inversion af kontrol afhænger flow af en objektgraf, som er skabt af en assembler-instans og muliggjort af objektinteraktioner defineret gennem abstraktioner. Bindingsprocessen opnås gennem afhængighedsinjektion, selvom nogle hævder, at brugen af ​​en servicelocator også giver omvendt kontrol.

Inversion af kontrol som en designguide tjener følgende formål:

  • Der er en afkobling af udførelsen af ​​en specifik opgave fra dens gennemførelse.
  • Hvert modul kan fokusere på, hvad det er beregnet til.
  • Moduler gør ingen antagelser om, hvad andre systemer gør, men stoler på deres kontrakter.
  • Udskiftning af moduler påvirker ikke andre moduler.

For mere information se.

Sidste opdatering: 03/11/2016

Afhængighedsinversionsprincip Afhængighedsinversionsprincippet bruges til at skabe løst koblede entiteter, der er nemme at teste, ændre og opdatere. Dette princip kan formuleres som følger:

Topniveaumoduler bør ikke afhænge af lavere niveaumoduler. Begge må afhænge af abstraktioner.

Abstraktioner bør ikke afhænge af detaljer. Detaljer må afhænge af abstraktioner.

For at forstå princippet skal du overveje følgende eksempel:

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

Bogklassen, som repræsenterer en bog, bruger ConsolePrinter-klassen til at udskrive. Med denne definition afhænger Book-klassen af ​​ConsolePrinter-klassen. Desuden definerede vi strengt, at udskrivning af en bog kun kan udføres på konsollen ved hjælp af ConsolePrinter-klassen. Andre muligheder, for eksempel output til en printer, output til en fil eller brug af nogle grafiske grænsefladeelementer - alt dette er udelukket i dette tilfælde. Bogudskrivningsabstraktionen er ikke adskilt fra detaljerne i ConsolePrinter-klassen. Alt dette er en overtrædelse af afhængighedsinversionsprincippet.

Lad os nu prøve at bringe vores klasser i overensstemmelse med princippet om afhængighedsinversion, der adskiller abstraktioner fra implementering på lavt niveau:

Interface 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(string text) ( Console.WriteLine("Print to console"); ) ) class HtmlPrinter: IPrinter ( public void Print(string text) ( Console.WriteLine("Udskriv i html"); ) )

Bogtrykabstraktionen er nu adskilt fra de konkrete implementeringer. Som et resultat afhænger både Book-klassen og ConsolePrinter-klassen af ​​IPrinter-abstraktionen. Derudover kan vi nu også skabe yderligere lav-niveau implementeringer af IPrinter abstraktion og dynamisk anvende dem i programmet:

Bogbog = ny bog(ny ConsolePrinter()); bog.Print(); bog.Printer = ny HtmlPrinter(); bog.Print();

Afhængighedsinversion er et af de vigtigste programmeringssprog. Der er overraskende få beskrivelser af dette formsprog (princippet) på det russisksprogede internet. Så jeg besluttede at prøve at lave en beskrivelse. Jeg vil lave eksemplerne i Java; i øjeblikket er det nemmere for mig, selvom princippet om afhængighedsinversion er gældende for ethvert programmeringssprog.

Denne beskrivelse er udviklet sammen med Vladimir Matveev som forberedelse til klasser med studerende, der studerer Java.

Andre artikler fra denne serie:

Lad mig starte med definitionen af ​​"afhængighed". Hvad er afhængighed? Hvis din kode bruger en klasse internt eller eksplicit kalder en statisk metode for en eller anden klasse eller funktion, er dette en afhængighed. Lad mig forklare med eksempler:

Herunder opretter klasse A, inde i en metode kaldet someMethod(), eksplicit et objekt af klasse B og kalder dets metode someMethodOfB()

Offentlig klasse A ( void someMethod() ( B b = new B(); b.someMethodOfB(); ) )

På samme måde, for eksempel, får klasse B eksplicit adgang til de statiske felter og metoder i System-klassen:

Offentlig klasse B ( void someMethodOfB() ( System.out.println("Hej verden"); ) )

I alle tilfælde, når en klasse (type A) uafhængigt opretter en klasse (type B) eller eksplicit får adgang til statiske felter eller klassemedlemmer, kaldes dette lige afhængighed. De der. vigtigt: hvis en klasse i sig selv arbejder i sig selv med en anden klasse, er dette en afhængighed. Hvis det også skaber denne klasse inde i sig selv, så dette lige afhængighed.

Hvad er der galt med direkte afhængigheder? Direkte afhængigheder er dårlige, fordi en klasse, der selvstændigt skaber en anden klasse i sig selv, er "tæt" bundet til denne klasse. De der. hvis det udtrykkeligt er skrevet, at B = ny B(); , så vil klasse A altid fungere med klasse B og ingen anden klasse. Eller hvis der står System.out.println("..."); så vil klassen altid udsende til System.out og ingen andre steder.

For små klasser er afhængigheder ikke forfærdelige. Denne kode kan fungere ganske godt. Men i nogle tilfælde, for at din klasse A skal kunne arbejde universelt i miljøet af forskellige klasser, kan det kræve andre implementeringer af klasser - afhængigheder. De der. For eksempel behøver du ikke klasse B, men en anden klasse med samme grænseflade, eller ikke System.out, men for eksempel output til en logger (for eksempel log4j).

Den direkte relation kan vises grafisk som følger:

De der. når du opretter klasse A i din kode: A a = new A(); faktisk skabes der ikke kun én klasse A, men et helt hierarki af afhængige klasser, et eksempel på det er på billedet nedenfor. Dette hierarki er "stift": uden at ændre kildekoden for individuelle klasser, kan du ikke erstatte nogen af ​​klasserne i hierarkiet. Derfor er klasse A i en sådan implementering dårligt tilpasningsdygtig til et skiftende miljø. Det kan højst sandsynligt ikke bruges i nogen anden kode end den specifikke kode, som du skrev den til.

For at afkoble klasse A fra specifikke afhængigheder, brug afhængighedsindsprøjtning. Hvad er afhængighedsinjektion? I stedet for eksplicit at oprette den nødvendige klasse i kode, overføres afhængigheder til klasse A gennem konstruktøren:

Offentlig klasse A ( privat finale B b; offentlig A(B b) ( this.b = b; ) public void someMethod() ( b.someMethodOfB(); ) )

At. klasse A modtager nu sin afhængighed gennem konstruktøren. Nu, for at oprette klasse A, skal du først oprette dens afhængige klasse. I dette tilfælde er det B:

B b = ny B(); A a = nyt A(b); a.someMethod();

Hvis den samme procedure gentages for alle klasser, dvs. videregive en instans af klasse D til konstruktøren af ​​klasse B, dens afhængigheder E og F til konstruktøren af ​​klasse D osv., så får du kode, hvori alle afhængigheder oprettes i omvendt rækkefølge:

G g = ny G(); Hh = nyt H(); Ff = ny (g,h); E e = ny E(); Dd = ny D(e,f); Bb = nyt B(d); A a = nyt A(b); a.someMethod();

Dette kan vises grafisk på denne måde:

Hvis du sammenligner 2 billeder - billedet ovenfor med direkte afhængigheder og det andet billede med afhængighedsindsprøjtning - kan du se, at pilenes retning er ændret til den modsatte. Af denne grund kaldes formsproget "inversion" af afhængigheder. Med andre ord betyder afhængighedsinversion, at klassen ikke opretter afhængigheder alene, men modtager dem i den oprettede form i konstruktøren (eller på anden måde).

Hvorfor er afhængighedsinversion god? Med afhængighedsinversion kan du erstatte alle afhængigheder i en klasse uden at ændre dens kode. Det betyder, at din klasse A fleksibelt kan konfigureres til brug i et andet program end det, det oprindeligt er skrevet til. At. Princippet om afhængighedsinversion (nogle gange kaldet princippet om afhængighedsinjektion) er nøglen til at bygge fleksibel, modulær, genanvendelig kode.

Ulempen ved afhængighedsinjektion er også synlig ved første øjekast - genstande fra klasser designet ved hjælp af dette mønster er arbejdskrævende at konstruere. Derfor bruges afhængighedsinjektion normalt sammen med et eller andet bibliotek designet til at lette denne opgave. For eksempel et af Google Guice-bibliotekerne. Cm..

© 2023 skudelnica.ru -- Kærlighed, forræderi, psykologi, skilsmisse, følelser, skænderier