CodeGym/Java kursus/Modul 3/Afhængighedsinversion

Afhængighedsinversion

Ledig

9.1 Afhængighedsinversion

Husk, vi sagde engang, at i en serverapplikation kan du ikke bare oprette streams gennem new Thread().start()? Kun beholderen skal skabe tråde. Vi vil nu udvikle denne idé yderligere.

Alle objekter bør også kun oprettes af containeren . Vi taler naturligvis ikke om alle objekter, men derimod om de såkaldte forretningsobjekter. De omtales også ofte som skraldespande. Benene til denne tilgang vokser fra det femte princip i SOLID, som kræver at slippe af med klasser og flytte til grænseflader:

  • Topniveaumoduler bør ikke afhænge af lavere niveaumoduler. Både disse og andre burde afhænge af abstraktioner.
  • Abstraktioner bør ikke afhænge af detaljer. Implementeringen skal afhænge af abstraktionen.

Moduler bør ikke indeholde referencer til specifikke implementeringer, og alle afhængigheder og interaktioner mellem dem bør udelukkende bygges på baggrund af abstraktioner (det vil sige grænseflader). Selve essensen af ​​denne regel kan skrives i én sætning: alle afhængigheder skal være i form af grænseflader .

På trods af dens grundlæggende karakter og tilsyneladende enkelhed overtrædes denne regel oftest. Nemlig hver gang, når vi bruger den nye operatør i programmets/modulets kode og opretter et nyt objekt af en bestemt type, så dannes afhængigheden af ​​implementeringen i stedet for at være afhængig af interfacet.

Det er klart, at dette ikke kan undgås, og objekter skal skabes et sted. Men i det mindste skal du minimere antallet af steder, hvor dette gøres, og hvor klasser er eksplicit specificeret, samt lokalisere og isolere sådanne steder, så de ikke er spredt ud over programkoden.

En meget god løsning er den skøre idé om at koncentrere skabelsen af ​​nye objekter inden for specialiserede objekter og moduler - fabrikker, servicelokaliser, IoC-containere.

På en måde følger en sådan beslutning Single Choice-princippet, som siger: "Når et softwaresystem skal understøtte mange alternativer, bør deres komplette liste kun være kendt af ét modul i systemet" .

Derfor, hvis det i fremtiden er nødvendigt at tilføje nye muligheder (eller nye implementeringer, som i tilfælde af at skabe nye objekter, vi overvejer), så vil det være nok kun at opdatere det modul, der indeholder disse oplysninger, og alle andre moduler vil forblive upåvirket og vil kunne fortsætte deres arbejde som normalt.

Eksempel 1

new ArrayList I stedet for at skrive noget som , ville det give mening List.new()for JDK at give dig den korrekte implementering af et blad: ArrayList, LinkedList eller endda ConcurrentList.

For eksempel ser compileren, at der er kald til objektet fra forskellige tråde og sætter en trådsikker implementering der. Eller for mange indstik i midten af ​​arket, så vil implementeringen være baseret på LinkedList.

Eksempel 2

Det er f.eks. allerede sket med sorteringer. Hvornår har du sidst skrevet en sorteringsalgoritme til at sortere en samling? I stedet bruger alle nu metoden Collections.sort(), og elementerne i samlingen skal understøtte Comparable-grænsefladen (comparable).

Hvis sort()du videregiver en samling på mindre end 10 elementer til metoden, er det sagtens muligt at sortere den med en boblesortering (Bubble sort), og ikke Quicksort.

Eksempel 3

Compileren ser allerede, hvordan du sammenkæder strenge og vil erstatte din kode med StringBuilder.append().

9.2 Afhængighedsinversion i praksis

Nu det mest interessante: lad os tænke på, hvordan vi kan kombinere teori og praksis. Hvordan kan moduler korrekt oprette og modtage deres "afhængigheder" og ikke overtræde afhængighedsinversion?

For at gøre dette, når du designer et modul, skal du selv bestemme:

  • hvad modulet gør, hvilken funktion det udfører;
  • så har modulet brug for fra sit miljø, det vil sige hvilke objekter/moduler det skal håndtere;
  • Og hvordan får han det?

For at overholde principperne for Dependency Inversion, skal du helt sikkert beslutte, hvilke eksterne objekter dit modul bruger, og hvordan det vil få referencer til dem.

Og her er følgende muligheder:

  • modulet selv skaber objekter;
  • modulet tager genstande fra beholderen;
  • modulet aner ikke, hvor objekterne kommer fra.

Problemet er, at for at oprette et objekt, skal du kalde en konstruktør af en bestemt type, og som et resultat vil modulet ikke afhænge af grænsefladen, men af ​​den specifikke implementering. Men hvis vi ikke ønsker, at objekter skal oprettes eksplicit i modulkoden, så kan vi bruge Factory Method- mønsteret .

"Bundlinjen er, at i stedet for direkte at instansiere et objekt via nyt, giver vi klientklassen en eller anden grænseflade til at skabe objekter. Da en sådan grænseflade altid kan tilsidesættes med det rigtige design, får vi en vis fleksibilitet, når vi bruger moduler på lavt niveau. i moduler på højt niveau" .

I tilfælde, hvor det er nødvendigt at oprette grupper eller familier af relaterede objekter, bruges en abstrakt fabrik i stedet for en fabriksmetode .

9.3 Brug af Service Locator

Modulet tager de nødvendige genstande fra den, der allerede har dem. Det antages, at systemet har et eller andet lager af objekter, hvor moduler kan "sætte" deres objekter og "tage" objekter fra depotet.

Denne tilgang er implementeret af Service Locator-mønsteret , hvis hovedidé er, at programmet har et objekt, der ved, hvordan man får alle de afhængigheder (tjenester), der kan være nødvendige.

Den største forskel fra fabrikker er, at Service Locator ikke opretter objekter, men faktisk allerede indeholder instansierede objekter (eller ved hvor/hvordan man får dem, og hvis den opretter, så kun én gang ved det første opkald). Fabrikken ved hvert opkald opretter et nyt objekt, som du får fuldt ejerskab til, og du kan gøre, hvad du vil med det.

Vigtigt ! Servicelocatoren producerer referencer til de samme allerede eksisterende objekter . Derfor skal du være meget forsigtig med de genstande, der udstedes af Service Locator, da en anden kan bruge dem på samme tid som dig.

Objekter i Service Locator kan tilføjes direkte gennem konfigurationsfilen, og faktisk på enhver måde, der er praktisk for programmøren. Selve Service Locator kan være en statisk klasse med et sæt statiske metoder, en singleton eller en grænseflade og kan videregives til de påkrævede klasser via en konstruktør eller metode.

Service Locator kaldes nogle gange et anti-mønster og frarådes (fordi den skaber implicitte forbindelser og kun giver et godt design). Du kan læse mere fra Mark Seaman:

9.4 Afhængighedsinjektion

Modulet er overhovedet ikke ligeglad med at "mine" afhængigheder. Den bestemmer kun, hvad den skal bruge for at fungere, og alle de nødvendige afhængigheder leveres (introduceret) udefra af en anden.

Det er det, der kaldes - Dependency Injection. Typisk sendes de nødvendige afhængigheder enten som konstruktørparametre (Constructor Injection) eller gennem klassemetoder (Setter-injection).

Denne tilgang inverterer processen med at skabe afhængigheder - i stedet for selve modulet styres skabelsen af ​​afhængigheder af nogen udefra. Modulet fra den aktive udsender af objekter bliver passivt - det er ikke ham, der skaber, men andre skaber for ham.

Denne retningsændring kaldes Inversion of Control eller Hollywood-princippet - "Ring ikke til os, vi ringer til dig."

Dette er den mest fleksible løsning, der giver modulerne den største autonomi . Vi kan sige, at kun det fuldt ud implementerer "Single Responsibility Principle" - modulet skal være fuldstændig fokuseret på at udføre sit arbejde godt og ikke bekymre sig om andet.

At forsyne modulet med alt, hvad der er nødvendigt for arbejdet, er en separat opgave, som skal håndteres af den relevante "specialist" (normalt er en bestemt beholder, en IoC-beholder, ansvarlig for styring af afhængigheder og deres implementering).

Faktisk er alt her som i livet: I en velorganiseret virksomhed programmerer programmører, og skriveborde, computere og alt, hvad de skal bruge til arbejdet, købes og stilles til rådighed af kontorchefen. Eller, hvis du bruger metaforen for programmet som konstruktør, skal modulet ikke tænke på ledninger, en anden er involveret i at samle konstruktøren, og ikke delene selv.

Det ville ikke være en overdrivelse at sige, at brugen af ​​grænseflader til at beskrive afhængigheder mellem moduler (Dependency Inversion) + den korrekte oprettelse og indsprøjtning af disse afhængigheder (primært Dependency Injection) er nøgleteknikker til afkobling .

De tjener som grundlaget, hvorpå den løse kobling af koden, dens fleksibilitet, modstand mod ændringer, genbrug, og uden hvilken alle andre teknikker ikke giver mening. Dette er grundlaget for løs kobling og god arkitektur.

Princippet om Inversion of Control (sammen med Dependency Injection and Service Locator) er beskrevet detaljeret af Martin Fowler. Der er oversættelser af begge hans artikler: "Inversion of Control Containers and the Dependency Injection pattern" og "Inversion of Control" .

Kommentarer
  • Populær
  • Ny
  • Gammel
Du skal være logget ind for at skrive en kommentar
Denne side har ingen kommentarer endnu