Введение в сервисы

В предыдущей части мы рассмотрели простой пример использования внедрения зависимостей без каких-либо специальных средств и библиотек .NET. Этот пример показывает, как минимум то, что внедрение зависимостей (dependency injection) — это набор принципов и шаблонов проектирования, а не какая-то особенность или библиотека .NET. Тем не менее, разработчики .NET/C# постарались сделать нашу с вами работу с DI наиболее комфортной и удобной, предоставив нам ряд классов и интерфейсов, позволяющих использовать возможности DI с минимальными затратами времени на разработку. И сегодня мы перепишем наш пример, используя те возможности, которые нам предлагает платформа .NET

Microsoft.Extensions.DependencyInjection

Все необходимые для работы классы и абстракции (интерфейсы) для работы с DI в .NET расположены в пакете Microsoft.Extensions.DependencyInjection. Следует отметить, что не во всех типах проектов этот пакет подключается по умолчанию. Например, в обычном консольном приложении, с которым мы работаем, такой пакет не подключен, поэтому его необходимо установить через менеджер пакетов NuGet:

В этом пакете содержатся три основных интерфейса с которыми мы будем дальше работать:

  • IServiceCollection — определяет контракт для коллекции дескрипторов сервисов.
  • IServiceProvider — определяет механизм получения объекта сервиса.
  • ServiceDescriptor — описывает сервис.

Что касается терминологии, то частов можно встретить такие названия, как службы, сервисы или просто — зависимости. В контексте изучения DI эти понятия равнозначны, поэтому здесь и далее мы будем использовать название сервис.

Основной принцип работы при использовании пакета Microsoft.Extensions.DependencyInjection заключается в следующем:

  1. Разработанные сервисы регистрируются в IServiceCollection
  2. После того, как все сервисы будут зарегистрированы мы должны получить ссылку на объект, реализующий IServiceProvider. Этот объект служит для получения информации о зарегистрированных службах и используется, как указано выше, для получения службы или, как ещё говорят, для разрешения зависимостей. То есть уже не мы создаем объекты-зависимости, как в прошлый раз, а именно провайдер будет брать на себя эту работу.

Попробуем переписать наш пример, используя пакет Microsoft.Extensions.DependencyInjection.

Пример использования коллекции (ServiceCollection) и провайдера сервисов (IServiceProvider)

Итак, на данный момент у нас уже имеется интерфейс

public interface IMessageWriter
{
    public void SendMessage(string text);
}

который и будет выступать у нас в роли сервиса. Две реализации сервиса:

public class MessageWriter: IMessageWriter
{
    public void SendMessage(string text)
    {
        Console.WriteLine(text);
    }
}

public class ColorMessageWriter : IMessageWriter
{
    public void SendMessage(string text)
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine(text);
        Console.ForegroundColor = ConsoleColor.White;
    }
}

а также класс, использующий наш сервис в работе. Сейчас нам остается только продемонстрировать пример того, как будет выглядеть наше приложение с использованием новых для нас объектов.

using Microsoft.Extensions.DependencyInjection;
namespace Example1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            ServiceCollection services = new ServiceCollection(); 

            //определяем объект какого типа необходимо создать
            Type type;
            string typeName;
            if (args.Length == 0) //аргументы командной строки не задана - используем тип по умолчанию
                typeName = "Example1.MessageWriter";
            else
                typeName = args[0];

            //пытаемся получить тип данных, объект которого необходимо создать
            type = Type.GetType(typeName, throwOnError: true);

            services.AddSingleton<Calculator>();
            services.AddSingleton(implementationFactory: _ => (IMessageWriter)Activator.CreateInstance(type));
            IServiceProvider serviceProvider = services.BuildServiceProvider();

            Calculator? calculator = serviceProvider.GetService<Calculator>();
            if (calculator != null)
            {
                calculator.Sum(10, 20);
                calculator.Sum(1, 2);
            }
        }
    }
}

Рассмотрим этот пример подробно. Вначале мы создаем новую коллекцию сервисов в нашем приложении:

ServiceCollection services = new ServiceCollection();

Далее, как и в прошлой части, мы определяем тип реализации нашего сервиса IMessageWriter, который будет использоваться при работе приложения.

//определяем объект какого типа необходимо создать
Type type;
string typeName;
if (args.Length == 0) //аргументы командной строки не задана - используем тип по умолчанию
    typeName = "Example1.MessageWriter";
else
    typeName = args[0];

//пытаемся получить тип данных, объект которого необходимо создать
type = Type.GetType(typeName, throwOnError: true);

Далее, мы добавляем сервисы в нашу коллекцию:

services.AddSingleton<Calculator>();
services.AddSingleton(implementationFactory: _ => (IMessageWriter)Activator.CreateInstance(type));

Здесь стоит обратить внимание на то, что наш класс Calculator в приложении также выступает в роли сервиса. Сделано это для того, чтобы переложить создание нового экземпляра Calculator полностью переложить на платформу. При регистрации сервиса IMessageWriter мы использовали одну из версий метода AddSingleton() в качестве параметра для которого мы передали делегат в котором создается очередной объект сервиса.

Далее, мы создаем объект провайдера сервисов:

IServiceProvider serviceProvider = services.BuildServiceProvider();

После этого действия мы можем пользоваться провайдером — запрашивать необходимые сервисы и использовать их. А далее происходит та самая «магия» Dependency Injection — мы не создаем самостоятельно никаких объектов, а просто указываем провайдеру какой сервис нам необходим для работы:

Calculator? calculator = serviceProvider.GetService<Calculator>();

в итоге, провайдер самостоятельно определит все зависимости, которые необходимы для запрашиваемого сервиса, создаст их объекты, передаст в запрашиваемый сервис и вернет нам полностью готовый для работы сервис. Ну, а далее — мы просто используем наш сервис в работе:

if (calculator != null)
{
    calculator.Sum(10, 20);
    calculator.Sum(1, 2);
}

Таким образом, используя ServiceCollection и IServiceProvider мы избавляемся от необходимости вручную определять все зависимости — теперь у нас имеется всего одна точка регистрации необходимых сервисов и все, что от нас требуется — зарегистрировать необходимые для приложения сервисы, причем, очередность регистрации сервисов к коллекции не имеет значения.

Итого

Сегодня мы рассмотрели пример Dependency Injection с использованием средств, доступных в .NET. Для использования механизмов DI мы должны создать коллекцию сервисов (ServiceCollection) и добавить в ней необходимые для работы приложения сервисы, создать провайдер сервисов (IServiceProvider) и запросить необходимый сервис, используя методы провайдера. Провайдер сервисов самостоятельно разрешит все зависимости в приложении и вернет нам готовый к работе сервис.

Подписаться
Уведомить о
guest
0 Комментарий
Старые
Новые Популярные
Межтекстовые Отзывы
Посмотреть все комментарии