Как да използвам инверсията на контрола в C #

Както обръщането на управление, така и инжектирането на зависимости ви позволяват да прекъснете зависимостите между компонентите във вашето приложение и да улесните приложението да тества и поддържа. Инверсията на контрола и впръскването на зависимостта обаче не са еднакви - има тънки разлики между двете.

В тази статия ще разгледаме инверсията на контролния модел и ще разберем как се различава от инжектирането на зависимости със съответните примери на код в C #.

За да работите с примерите за кодове, предоставени в тази статия, трябва да имате Visual Studio 2019 инсталиран във вашата система. Ако все още нямате копие, можете да изтеглите Visual Studio 2019 тук. 

Създайте проект за конзолно приложение в Visual Studio

Първо, нека създадем проект за приложение на конзола .NET Core в Visual Studio. Ако приемем, че Visual Studio 2019 е инсталиран във вашата система, следвайте стъпките, описани по-долу, за да създадете нов проект за приложение на конзола .NET Core в Visual Studio.

  1. Стартирайте Visual Studio IDE.
  2. Кликнете върху „Създаване на нов проект“.
  3. В прозореца „Създаване на нов проект“ изберете „Console App (.NET Core)“ от показания списък с шаблони.
  4. Щракнете върху Напред. 
  5. В показания след това прозорец „Конфигуриране на вашия нов проект“ посочете името и местоположението на новия проект.
  6. Щракнете върху Създаване. 

Това ще създаде нов проект за приложение на конзола .NET Core в Visual Studio 2019. Ще използваме този проект, за да изследваме инверсията на контрола в следващите раздели на тази статия.

Какво е инверсия на контрола?

Инверсията на управлението (IoC) е модел на проектиране, при който контролният поток на програма се обръща. Можете да се възползвате от инверсията на шаблона за управление, за да отделите компонентите на вашето приложение, да разменяте реализации на зависимости, да се присмивате на зависимости и да направите приложението си модулно и подлежащо на проверка.

Инжектирането на зависимост е подмножество на принципа на инверсия на контрола. С други думи, инжектирането на зависимост е само един от начините за осъществяване на инверсия на контрола. Можете също така да внедрите инверсия на управление, като използвате събития, делегати, шаблон на шаблон, фабричен метод или локатор на услуги, например.

Инверсията на модела за проектиране на управление гласи, че обектите не трябва да създават обекти, от които зависят, за да изпълняват някаква дейност. Вместо това те трябва да получат тези обекти от външна услуга или контейнер. Идеята е аналогична на холивудския принцип, който казва: "Не ни се обаждайте, ние ще ви се обадим." Като пример, вместо приложението да извиква методите в рамка, рамката ще извика изпълнението, предоставено от приложението. 

Пример за инверсия на управление в C #

Да предположим, че изграждате приложение за обработка на поръчки и бихте искали да приложите регистриране. За простота, нека приемем, че целта на регистрационния файл е текстов файл. Изберете току-що създадения проект за конзолно приложение в прозореца на Solution Explorer и създайте два файла, наречени ProductService.cs и FileLogger.cs.

    публичен клас ProductService

    {

        private readonly FileLogger _fileLogger = нов FileLogger ();

        public void Log (низ съобщение)

        {

            _fileLogger.Log (съобщение);

        }

    }

    публичен клас FileLogger

    {

        public void Log (низ съобщение)

        {

            Console.WriteLine ("Метод от вътрешния дневник на FileLogger.");

            LogToFile (съобщение);

        }

        private void LogToFile (низ съобщение)

        {

            Console.WriteLine ("Метод: LogToFile, Текст: {0}", съобщение);

        }

    }

Изпълнението, показано в предходния кодов фрагмент, е правилно, но има ограничение. Вие сте ограничени да регистрирате данни само в текстов файл. Не можете по никакъв начин да регистрирате данни в други източници на данни или различни цели на дневника.

Негъвкаво изпълнение на регистрацията

Какво ще стане, ако искате да регистрирате данни в таблица на базата данни? Съществуващата реализация няма да подкрепи това и ще бъдете принудени да промените изпълнението. Можете да промените изпълнението на класа FileLogger или да създадете нов клас, да речем, DatabaseLogger.

    публичен клас DatabaseLogger

    {

        public void Log (низ съобщение)

        {

            Console.WriteLine ("Метод от вътрешния дневник на DatabaseLogger.");

            LogToDatabase (съобщение);

        }

        private void LogToDatabase (низ съобщение)

        {

            Console.WriteLine ("Метод: LogToDatabase, Текст: {0}", съобщение);

        }

    }

Може дори да създадете екземпляр на класа DatabaseLogger вътре в класа ProductService, както е показано в кодовия фрагмент по-долу.

публичен клас ProductService

    {

        private readonly FileLogger _fileLogger = нов FileLogger ();

        private readonly DatabaseLogger _databaseLogger =

         нов DatabaseLogger ();

        public void LogToFile (низ съобщение)

        {

            _fileLogger.Log (съобщение);

        }

        public void LogToDatabase (низ съобщение)

        {

            _fileLogger.Log (съобщение);

        }

    }

Въпреки това, въпреки че това ще работи, какво, ако трябва да регистрирате данните на приложението си в EventLog? Вашият дизайн не е гъвкав и ще бъдете принудени да променяте класа ProductService всеки път, когато трябва да влезете в нова цел на регистрационния файл. Това е не само тромаво, но и ще ви затрудни изключително много с времето да управлявате клас ProductService.

Добавете гъвкавост с интерфейс 

Решението на този проблем е да се използва интерфейс, който конкретните класове на регистратора биха внедрили. Следният кодов фрагмент показва интерфейс, наречен ILogger. Този интерфейс ще бъде реализиран от двата конкретни класа FileLogger и DatabaseLogger.

публичен интерфейс ILogger

{

    void Log (низ съобщение);

}

Актуализираните версии на класовете FileLogger и DatabaseLogger са дадени по-долу.

публичен клас FileLogger: ILogger

    {

        public void Log (низ съобщение)

        {

            Console.WriteLine ("Метод от вътрешния дневник на FileLogger.");

            LogToFile (съобщение);

        }

        private void LogToFile (низ съобщение)

        {

            Console.WriteLine ("Метод: LogToFile, Текст: {0}", съобщение);

        }

    }

публичен клас DatabaseLogger: ILogger

    {

        public void Log (низ съобщение)

        {

            Console.WriteLine ("Метод от вътрешния дневник на DatabaseLogger.");

            LogToDatabase (съобщение);

        }

        private void LogToDatabase (низ съобщение)

        {

            Console.WriteLine ("Метод: LogToDatabase, Текст: {0}", съобщение);

        }

    }

Вече можете да използвате или промените конкретната реализация на интерфейса ILogger, когато е необходимо. Следният кодов фрагмент показва клас ProductService с изпълнение на метода Log.

публичен клас ProductService

    {

        public void Log (низ съобщение)

        {

            ILogger logger = нов FileLogger ();

            logger.Log (съобщение);

        }

    }

Дотук добре. Какво обаче, ако искате да използвате DatabaseLogger вместо FileLogger в метода Log на класа ProductService? Можете да промените изпълнението на метода Log в класа ProductService, за да отговорите на изискването, но това не прави дизайна гъвкав. Нека сега направим дизайна по-гъвкав, като използваме инверсия на управление и инжектиране на зависимост.

Обърнете контролата, като използвате инжектиране на зависимост

Следният кодов фрагмент илюстрира как можете да се възползвате от инжектирането на зависимости, за да предадете екземпляр на конкретен клас регистратор, използвайки инжектиране на конструктор.

публичен клас ProductService

    {

        частен ILogger _logger само за четене;

        публична ProductService (регистратор на ILogger)

        {

            _logger = регистратор;

        }

        public void Log (низ съобщение)

        {

            _logger.Log (съобщение);

        }

    }

И накрая, нека видим как можем да предадем реализация на интерфейса ILogger на клас ProductService. Следният кодов фрагмент показва как можете да създадете екземпляр на класа FileLogger и да използвате инжектор на конструктор, за да предадете зависимостта.

static void Main (низ [] аргументи)

{

    ILogger logger = нов FileLogger ();

    ProductService productService = нова ProductService (регистратор);

    productService.Log („Здравей, свят!“);

}

По този начин сме обърнали контрола. Класът ProductService вече не е отговорен за създаването на екземпляр на изпълнение на интерфейса ILogger или дори за вземане на решение коя реализация на интерфейса ILogger трябва да се използва.

Инверсията на контрол и инжектиране на зависимости ви помагат с автоматично създаване на екземпляри и управление на жизнения цикъл на вашите обекти. ASP.NET Core включва проста, вградена инверсия на контролен контейнер с ограничен набор от функции. Можете да използвате този вграден IoC контейнер, ако вашите нужди са прости или да използвате контейнер на трета страна, ако искате да използвате допълнителни функции.

Можете да прочетете повече за това как да работите с инверсия на контрол и инжектиране на зависимости в ASP.NET Core в по-ранната ми публикация тук.