Kako se koristi inverzija kontrole u C #

I inverzija upravljanja i ubrizgavanje ovisnosti omogućuju vam razbijanje ovisnosti između komponenata u vašoj aplikaciji i olakšavaju testiranje i održavanje vaše aplikacije. Međutim, inverzija kontrole i ubrizgavanje ovisnosti nisu isto - između njih postoje suptilne razlike.

U ovom ćemo članku ispitati inverziju kontrolnog uzorka i shvatiti kako se razlikuje od ubrizgavanja ovisnosti s relevantnim primjerima koda u C #.

Da biste radili s primjerima koda iz ovog članka, u sustavu biste trebali instalirati Visual Studio 2019. Ako još nemate kopiju, ovdje možete preuzeti Visual Studio 2019. 

Stvorite projekt aplikacije konzole u Visual Studiju

Prvo, kreirajmo projekt aplikacije .NET Core konzole u Visual Studiju. Pod pretpostavkom da je Visual Studio 2019 instaliran u vašem sustavu, slijedite korake opisane u nastavku da biste u Visual Studiju stvorili novi projektni program konzole .NET Core.

  1. Pokrenite Visual Studio IDE.
  2. Kliknite "Stvori novi projekt".
  3. U prozoru "Stvori novi projekt" s popisa predložaka odaberite "Aplikacija konzole (.NET Core)".
  4. Kliknite Dalje. 
  5. U sljedećem prozoru "Konfiguriranje novog projekta" navedite naziv i mjesto za novi projekt.
  6. Kliknite Stvori. 

To će stvoriti novi projekt aplikacije .NET Core konzole u Visual Studio 2019. Ovaj ćemo projekt koristiti za istraživanje inverzije kontrole u sljedećim odjeljcima ovog članka.

Što je inverzija kontrole?

Inverzija upravljanja (IoC) je obrazac dizajna u kojem je kontrolni tok programa obrnut. Možete iskoristiti inverziju kontrolnog uzorka za razdvajanje komponenata vaše aplikacije, zamjenu implementacija ovisnosti, ismijavanje ovisnosti i stvaranje aplikacije modularnom i provjerljivom.

Injekcija ovisnosti podskup je principa inverzije upravljanja. Drugim riječima, ubrizgavanje ovisnosti samo je jedan od načina provođenja inverzije kontrole. Također možete implementirati inverziju kontrole koristeći, na primjer, događaje, delegate, obrazac predloška, ​​tvorničku metodu ili lokator usluga.

Inverzija uzorka dizajna upravljanja navodi da objekti ne bi trebali stvarati objekte o kojima ovise za obavljanje neke aktivnosti. Umjesto toga, te bi predmete trebali dobiti iz vanjske službe ili kontejnera. Ideja je analogna hollywoodskom principu koji kaže: "Ne zovite nas, mi ćemo vas zvati." Kao primjer, umjesto da aplikacija poziva metode u okviru, okvir će pozvati implementaciju koju je osigurala aplikacija. 

Primjer inverzije upravljanja u C #

Pretpostavimo da gradite aplikaciju za obradu narudžbi i da biste željeli implementirati bilježenje. Radi jednostavnosti, pretpostavimo da je cilj dnevnika tekstualna datoteka. Odaberite projekt aplikacije konzole koji ste upravo kreirali u prozoru Solution Explorer i stvorite dvije datoteke pod nazivom ProductService.cs i FileLogger.cs.

    javna klasa ProductService

    {

        privatno samo za čitanje FileLogger _fileLogger = novi FileLogger ();

        javni void Log (string poruka)

        {

            _fileLogger.Log (poruka);

        }

    }

    javna klasa FileLogger

    {

        javni void Log (string poruka)

        {

            Console.WriteLine ("Metoda unutar dnevnika FileLogger.");

            LogToFile (poruka);

        }

        privatna praznina LogToFile (niz poruka)

        {

            Console.WriteLine ("Metoda: LogToFile, Tekst: {0}", poruka);

        }

    }

Implementacija prikazana u prethodnom isječku koda je točna, ali postoji ograničenje. Ograničeni ste na prijavu podataka samo u tekstualnu datoteku. Ne možete ni na koji način prijaviti podatke u druge izvore podataka ili različite ciljeve dnevnika.

Nefleksibilna primjena bilježenja

Što ako želite prijaviti podatke u tablicu baze podataka? Postojeća implementacija to ne bi podržala i bili biste prisiljeni promijeniti implementaciju. Možete promijeniti implementaciju klase FileLogger ili možete stvoriti novu klasu, recimo, DatabaseLogger.

    javna klasa DatabaseLogger

    {

        javni void Log (string poruka)

        {

            Console.WriteLine ("Metoda unutar dnevnika DatabaseLogger.");

            LogToDatabase (poruka);

        }

        privatna praznina LogToDatabase (niz poruka)

        {

            Console.WriteLine ("Metoda: LogToDatabase, Tekst: {0}", poruka);

        }

    }

Možete čak stvoriti instancu klase DatabaseLogger unutar klase ProductService kako je prikazano u isječku koda u nastavku.

javna klasa ProductService

    {

        privatno samo za čitanje FileLogger _fileLogger = novi FileLogger ();

        privatno samo za čitanje DatabaseLogger _databaseLogger =

         novi DatabaseLogger ();

        javna void LogToFile (niz poruka)

        {

            _fileLogger.Log (poruka);

        }

        javna void LogToDatabase (niz poruka)

        {

            _fileLogger.Log (poruka);

        }

    }

Međutim, iako bi to uspjelo, što ako biste trebali prijaviti podatke svoje aplikacije na EventLog? Vaš dizajn nije fleksibilan i bit ćete prisiljeni promijeniti klasu ProductService svaki put kad se trebate prijaviti na novi cilj dnevnika. To je ne samo glomazno već će vam i s vremenom izuzetno otežati upravljanje klasom ProductService.

Dodajte fleksibilnost sučeljem 

Rješenje ovog problema je uporaba sučelja koje bi konkretne klase dnevnika implementirale. Sljedeći isječak koda prikazuje sučelje nazvano ILogger. Ovo sučelje implementirale bi dvije konkretne klase FileLogger i DatabaseLogger.

javno sučelje ILogger

{

    void Log (string poruka);

}

Ažurirane verzije klasa FileLogger i DatabaseLogger date su u nastavku.

javna klasa FileLogger: ILogger

    {

        javni void Log (string poruka)

        {

            Console.WriteLine ("Metoda unutar dnevnika FileLogger.");

            LogToFile (poruka);

        }

        privatna praznina LogToFile (niz poruka)

        {

            Console.WriteLine ("Metoda: LogToFile, Tekst: {0}", poruka);

        }

    }

javna klasa DatabaseLogger: ILogger

    {

        javni void Log (string poruka)

        {

            Console.WriteLine ("Metoda unutar dnevnika DatabaseLogger.");

            LogToDatabase (poruka);

        }

        privatna praznina LogToDatabase (niz poruka)

        {

            Console.WriteLine ("Metoda: LogToDatabase, Tekst: {0}", poruka);

        }

    }

Sada možete koristiti ili promijeniti konkretnu implementaciju sučelja ILogger kad god je to potrebno. Sljedeći isječak koda prikazuje klasu ProductService s implementacijom metode Log.

javna klasa ProductService

    {

        javni void Log (string poruka)

        {

            ILogger logger = novi FileLogger ();

            logger.Log (poruka);

        }

    }

Zasada je dobro. Međutim, što ako biste željeli koristiti DatabaseLogger umjesto FileLogger u metodi Log klase ProductService? Možete promijeniti implementaciju metode Log u klasi ProductService kako bi udovoljili zahtjevu, ali to dizajn ne čini fleksibilnim. Učinimo sada dizajn fleksibilnijim koristeći inverziju upravljanja i ubrizgavanje ovisnosti.

Invertirajte kontrolu pomoću ubrizgavanja ovisnosti

Sljedeći isječak koda ilustrira kako možete iskoristiti ubrizgavanje ovisnosti za prosljeđivanje instance klase konkretnog zapisnika pomoću ubrizgavanja konstruktora.

javna klasa ProductService

    {

        privatno samo za čitanje ILogger _logger;

        javna ProductService (ILogger logger)

        {

            _logger = zapisničar;

        }

        javni void Log (string poruka)

        {

            _logger.Log (poruka);

        }

    }

Na kraju, pogledajmo kako možemo proslijediti implementaciju sučelja ILogger u klasu ProductService. Sljedeći isječak koda pokazuje kako možete stvoriti instancu klase FileLogger i koristiti ubrizgavanje konstruktora za prosljeđivanje ovisnosti.

statička praznina Main (string [] args)

{

    ILogger logger = novi FileLogger ();

    ProductService productService = nova ProductService (logger);

    productService.Log ("Pozdrav svijetu!");

}

Pritom smo obrnuli kontrolu. Klasa ProductService više nije odgovorna za stvaranje instance implementacije sučelja ILogger ili čak odlučivanja koju implementaciju sučelja ILogger treba koristiti.

Inverzija kontrole i ubrizgavanje ovisnosti pomažu vam u automatskoj instanci i upravljanju životnim ciklusom vaših objekata. ASP.NET Core uključuje jednostavnu, ugrađenu inverziju kontrolnog spremnika s ograničenim skupom značajki. Možete koristiti ovaj ugrađeni IoC spremnik ako su vaše potrebe jednostavne ili upotrijebiti spremnik treće strane ako želite iskoristiti dodatne značajke.

Više o tome kako raditi s inverzijom kontrole i ubrizgavanjem ovisnosti u ASP.NET Core možete pročitati u mom ranijem postu ovdje.