Programmering

Sådan bruges inversion af kontrol i C #

Både inversion af kontrol og afhængighedsinjektion giver dig mulighed for at bryde afhængigheder mellem komponenterne i din applikation og gøre din applikation lettere at teste og vedligeholde. Imidlertid er inversion af kontrol og afhængighedsinjektion ikke den samme - der er subtile forskelle mellem de to.

I denne artikel undersøger vi inversionen af ​​kontrolmønsteret og forstår, hvordan det adskiller sig fra afhængighedsinjektion med relevante kodeeksempler i C #.

For at arbejde med kodeeksemplerne i denne artikel skal du have Visual Studio 2019 installeret i dit system. Hvis du ikke allerede har en kopi, kan du downloade Visual Studio 2019 her.

Opret et konsolapplikationsprojekt i Visual Studio

Lad os først oprette et .NET Core-konsolapplikationsprojekt i Visual Studio. Forudsat at Visual Studio 2019 er installeret i dit system, skal du følge nedenstående trin for at oprette et nyt .NET Core-konsolapplikationsprojekt i Visual Studio.

  1. Start Visual Studio IDE.
  2. Klik på "Opret nyt projekt."
  3. I vinduet "Opret nyt projekt" skal du vælge "Konsolapp (.NET Core)" fra listen over skabeloner, der vises.
  4. Klik på Næste.
  5. I vinduet "Konfigurer dit nye projekt", der vises nedenfor, skal du angive navnet og placeringen for det nye projekt.
  6. Klik på Opret.

Dette opretter et nyt .NET Core-konsolapplikationsprojekt i Visual Studio 2019. Vi bruger dette projekt til at undersøge inversion af kontrol i de efterfølgende afsnit i denne artikel.

Hvad er inversion af kontrol?

Inversion of control (IoC) er et designmønster, hvor styringsflowet for et program er inverteret. Du kan drage fordel af inversionen af ​​kontrolmønster til at afkoble komponenterne i din applikation, bytte afhængighedsimplementeringer, mock-afhængigheder og gøre din applikation modulær og testbar.

Afhængighedsinjektion er en delmængde af inversion af kontrolprincippet. Med andre ord er afhængighedsinjektion kun en måde at implementere inversion af kontrol på. Du kan f.eks. Implementere inversion af kontrol ved hjælp af begivenheder, delegerede, skabelonmønster, fabriksmetode eller servicelokator.

Inversionen af ​​kontroldesignmønsteret siger, at objekter ikke skal skabe objekter, som de er afhængige af for at udføre en aktivitet. I stedet skal de få disse objekter fra en ekstern tjeneste eller en container. Ideen er analog med Hollywood-princippet, der siger: "Ring ikke til os, vi ringer til dig." Som et eksempel, i stedet for at applikationen kalder metoderne i en ramme, vil rammen kalde den implementering, der er leveret af applikationen.

Inversion af kontroleksempel i C #

Antag, at du bygger en ordrebehandlingsapplikation, og at du gerne vil implementere logning. Af enkelheds skyld antager vi, at logmålet er en tekstfil. Vælg det konsolapplikationsprojekt, du lige har oprettet i vinduet Solution Explorer, og opret to filer, der hedder ProductService.cs og FileLogger.cs.

  offentlig klasse ProductService

    {

privat readonly FileLogger _fileLogger = ny FileLogger ();

offentlig tomrumslog (strengbesked)

        {

_fileLogger.Log (besked);

        }

    }

offentlig klasse FileLogger

    {

offentlig tomrumslog (strengbesked)

        {

Console.WriteLine ("Inside Log-metode til FileLogger.");

LogToFile (besked);

        }

privat ugyldigt LogToFile (streng besked)

        {

Console.WriteLine ("Metode: LogToFile, tekst: {0}", besked);

        }

    }

Implementeringen vist i det foregående kodestykke er korrekt, men der er en begrænsning. Du er begrænset til kun at logge data i en tekstfil. Du kan ikke på nogen måde logge data til andre datakilder eller forskellige logmål.

En ufleksibel implementering af logning

Hvad hvis du ville logge data i en databasetabel? Den eksisterende implementering understøtter ikke dette, og du vil være tvunget til at ændre implementeringen. Du kan ændre implementeringen af ​​FileLogger-klassen, eller du kan oprette en ny klasse, f.eks. DatabaseLogger.

    offentlig klasse DatabaseLogger

    {

offentlig tomrumslog (strengbesked)

        {

Console.WriteLine ("Inside Log-metode til DatabaseLogger.");

LogToDatabase (besked);

        }

privat ugyldigt LogToDatabase (streng besked)

        {

Console.WriteLine ("Metode: LogToDatabase, tekst: {0}", besked);

        }

    }

Du kan endda oprette en forekomst af DatabaseLogger-klassen inden for ProductService-klassen som vist i kodestykket nedenfor.

offentlig klasse ProductService

    {

privat readonly FileLogger _fileLogger = ny FileLogger ();

privat readonly DatabaseLogger _databaseLogger =

ny DatabaseLogger ();

offentlig ugyldig LogToFile (streng besked)

        {

_fileLogger.Log (besked);

        }

offentlig ugyldig LogToDatabase (streng besked)

        {

_fileLogger.Log (besked);

        }

    }

Men selvom dette ville fungere, hvad hvis du havde brug for at logge din applikations data til EventLog? Dit design er ikke fleksibelt, og du vil blive tvunget til at ændre ProductService-klassen, hver gang du har brug for at logge på et nyt logmål. Dette er ikke kun besværligt, men det vil også gøre det ekstremt vanskeligt for dig at administrere ProductService-klassen over tid.

Tilføj fleksibilitet med en grænseflade

Løsningen på dette problem er at bruge en grænseflade, som de konkrete loggerklasser ville implementere. Følgende kodestykke viser en grænseflade kaldet ILogger. Denne grænseflade ville blive implementeret af de to konkrete klasser FileLogger og DatabaseLogger.

offentlig grænseflade ILogger

{

ugyldig log (strengbesked);

}

De opdaterede versioner af klasserne FileLogger og DatabaseLogger er angivet nedenfor.

offentlig klasse FileLogger: ILogger

    {

offentlig tomrumslog (strengbesked)

        {

Console.WriteLine ("Inside Log-metode til FileLogger.");

LogToFile (besked);

        }

privat ugyldigt LogToFile (streng besked)

        {

Console.WriteLine ("Metode: LogToFile, tekst: {0}", besked);

        }

    }

offentlig klasse DatabaseLogger: ILogger

    {

offentlig tomrumslog (strengbesked)

        {

Console.WriteLine ("Inside Log-metode til DatabaseLogger.");

LogToDatabase (besked);

        }

privat ugyldigt LogToDatabase (streng besked)

        {

Console.WriteLine ("Metode: LogToDatabase, tekst: {0}", besked);

        }

    }

Du kan nu bruge eller ændre den konkrete implementering af ILogger-grænsefladen, når det er nødvendigt. Følgende kodestykke viser klassen ProductService med en implementering af Log-metoden.

offentlig klasse ProductService

    {

offentlig tomrumslog (strengbesked)

        {

ILogger logger = ny FileLogger ();

logger.Log (besked);

        }

    }

Så langt så godt. Men hvad hvis du gerne vil bruge DatabaseLogger i stedet for FileLogger i Log-metoden i klassen ProductService? Du kan ændre implementeringen af ​​Log-metoden i ProductService-klassen for at imødekomme kravet, men det gør ikke designet fleksibelt. Lad os nu gøre designet mere fleksibelt ved hjælp af inversion af kontrol og afhængighedsinjektion.

Inverter kontrollen ved hjælp af afhængighedsinjektion

Følgende kodestykke illustrerer, hvordan du kan drage fordel af afhængighedsinjektion til at passere en forekomst af en konkret loggerklasse ved hjælp af konstruktørinjektion.

offentlig klasse ProductService

    {

privat readonly ILogger _logger;

offentlig ProductService (ILogger logger)

        {

_logger = logger;

        }

offentlig tomrumslog (strengbesked)

        {

_logger.Log (besked);

        }

    }

Lad os endelig se, hvordan vi kan overføre en implementering af ILogger-interface til ProductService-klassen. Følgende kodestykke viser, hvordan du kan oprette en forekomst af FileLogger-klassen og bruge konstruktørinjektion til at passere afhængigheden.

statisk ugyldigt Main (streng [] args)

{

ILogger logger = ny FileLogger ();

ProductService productService = ny ProductService (logger);

productService.Log ("Hello World!");

}

Dermed har vi omvendt kontrollen. ProductService-klassen er ikke længere ansvarlig for at oprette en forekomst af en implementering af ILogger-grænsefladen eller endda beslutte, hvilken implementering af ILogger-grænsefladen skal bruges.

Inversion af kontrol og afhængighedsindsprøjtning hjælper dig med automatisk instantiering og styring af livscyklus af dine objekter. ASP.NET Core inkluderer en enkel, indbygget inversion af kontrolcontainer med et begrænset sæt funktioner. Du kan bruge denne indbyggede IoC-container, hvis dine behov er enkle, eller bruge en tredjepartscontainer, hvis du vil udnytte yderligere funktioner.

Du kan læse mere om, hvordan du arbejder med inversion af kontrol og afhængighedsinjektion i ASP.NET Core i mit tidligere indlæg her.