Содержание
В предыдущей части мы рассмотрели простой пример использования внедрения зависимостей без каких-либо специальных средств и библиотек .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 заключается в следующем:
- Разработанные сервисы регистрируются в
IServiceCollection - После того, как все сервисы будут зарегистрированы мы должны получить ссылку на объект, реализующий
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) и запросить необходимый сервис, используя методы провайдера. Провайдер сервисов самостоятельно разрешит все зависимости в приложении и вернет нам готовый к работе сервис.
