Внедрение зависимостей
Совет
Это содержимое является фрагментом из электронной книги, шаблонов корпоративных приложений с помощью .NET MAUI, доступных в .NET Docs или в виде бесплатного скачиваемого PDF-файла, который можно прочитать в автономном режиме.
Как правило, конструктор класса вызывается при создании экземпляра объекта и все значения, необходимые объекту, передаются в качестве аргументов конструктору. Это пример внедрения зависимостей, известный как внедрение конструктора. Зависимости, необходимые для объекта, внедряются в конструктор.
При указании зависимостей в качестве типов интерфейса внедрение зависимостей позволяет развязывание конкретных типов от кода, зависящее от этих типов. Обычно он использует контейнер, содержащий список регистраций и сопоставлений между интерфейсами и абстрактными типами, а также конкретные типы, реализующие или расширяющие эти типы.
Существуют и другие типы внедрения зависимостей, такие как внедрение метода задания свойств и внедрение вызовов методов, но они реже встречаются. Поэтому эта глава будет сосредоточена исключительно на выполнении внедрения конструктора с контейнером внедрения зависимостей.
Введение в внедрение зависимостей
Внедрение зависимостей — это специализированная версия шаблона инверсии элемента управления (IoC), при которой превратимая проблема — это процесс получения требуемой зависимости. При внедрении зависимостей другой класс отвечает за внедрение зависимостей в объект во время выполнения. В следующем примере кода показано, как ProfileViewModel
класс структурирован при использовании внедрения зависимостей:
private readonly ISettingsService _settingsService;
private readonly IAppEnvironmentService _appEnvironmentService;
public ProfileViewModel(
IAppEnvironmentService appEnvironmentService,
IDialogService dialogService,
INavigationService navigationService,
ISettingsService settingsService)
: base(dialogService, navigationService, settingsService)
{
_appEnvironmentService = appEnvironmentService;
_settingsService = settingsService;
// Omitted for brevity
}
Конструктор ProfileViewModel
получает несколько экземпляров объектов интерфейса в качестве аргументов, введенных другим классом. Единственная зависимость в ProfileViewModel
классе зависит от типов интерфейса. ProfileViewModel
Поэтому у класса нет знаний о классе, ответственном за создание экземпляров объектов интерфейса. Класс, отвечающий за создание экземпляров объектов интерфейса и его вставку в ProfileViewModel
класс, называется контейнером внедрения зависимостей.
Контейнеры внедрения зависимостей сокращают связь между объектами, предоставляя объект для создания экземпляров классов и управления их временем существования на основе конфигурации контейнера. Во время создания объекта контейнер внедряет в него все зависимости, необходимые объекту. Если эти зависимости еще не созданы, контейнер сначала создает и разрешает их зависимости.
Существует несколько преимуществ использования контейнера внедрения зависимостей:
- Контейнер удаляет необходимость в поиске зависимостей класса и управлении его временем существования.
- Контейнер позволяет сопоставлять реализованные зависимости, не затрагивая класс.
- Контейнер упрощает тестирование, позволяя издеваться над зависимостями.
- Контейнер повышает удобство обслуживания, позволяя новым классам легко добавляться в приложение.
В контексте приложения .NET MAUI , использующего MVVM, контейнер внедрения зависимостей обычно будет использоваться для регистрации и разрешения представлений, регистрации и разрешения моделей представлений, а также для регистрации служб и внедрения их в модели просмотра.
В .NET доступно множество контейнеров внедрения зависимостей; Приложение eShop с несколькими платформами используется Microsoft.Extensions.DependencyInjection
для управления экземплярами представлений, моделей просмотра и классов служб в приложении. Microsoft.Extensions.DependencyInjection
упрощает создание слабо связанных приложений и предоставляет все функции, часто найденные в контейнерах внедрения зависимостей, включая методы для регистрации сопоставлений типов и экземпляров объектов, разрешения объектов, управления временем существования объектов и внедрения зависимых объектов в конструкторы объектов, которые он разрешает. Дополнительные сведения см Microsoft.Extensions.DependencyInjection
. в статье об внедрении зависимостей в .NET.
В .NET MAUIMauiProgram
класс вызовет CreateMauiApp
метод для создания MauiAppBuilder
объекта. Объект MauiAppBuilder
имеет Services
свойство типа IServiceCollection
, которое предоставляет место для регистрации наших компонентов, таких как представления, модели представления и службы для внедрения зависимостей. Все компоненты, зарегистрированные в свойстве Services
, будут предоставлены контейнеру внедрения зависимостей при вызове MauiAppBuilder.Build
метода.
Во время выполнения контейнер должен знать, какая реализация служб запрашивается, чтобы создать экземпляры для запрошенных объектов. Чтобы создать экземпляр ProfileViewModel
объекта, INavigationService
IDialogService
IAppEnvironmentService
необходимо разрешить в приложении eShop с несколькими платформами , а также интерфейсы и ISettingsService
интерфейсы. Это включает в себя контейнер, выполняющий следующие действия:
- Решение о создании экземпляра объекта, реализующего интерфейс. Это называется регистрацией.
- Создание экземпляра объекта, реализующего необходимый интерфейс и
ProfileViewModel
объект. Это называется разрешением.
В конечном итоге приложение завершит использование ProfileViewModel
объекта, и оно станет доступным для сборки мусора. На этом этапе сборщик мусора должен удалить любые кратковременные реализации интерфейса, если другие классы не используют один и тот же экземпляр.
Регистрация
Перед внедрением зависимостей в объект необходимо сначала зарегистрировать типы зависимостей в контейнере. Регистрация типа включает передачу контейнера интерфейсу и конкретному типу, реализующего интерфейс.
Существует два способа регистрации типов и объектов в контейнере с помощью кода:
- Зарегистрируйте тип или сопоставление с контейнером. Это называется временной регистрацией. При необходимости контейнер создаст экземпляр указанного типа.
- Зарегистрируйте существующий объект в контейнере в качестве одного. При необходимости контейнер вернет ссылку на существующий объект.
Примечание.
Контейнеры внедрения зависимостей не всегда подходят. Внедрение зависимостей представляет дополнительную сложность и требования, которые могут быть не подходящими или полезными для небольших приложений. Если класс не имеет зависимостей или не является зависимостью для других типов, он может не иметь смысла поместить его в контейнер. Кроме того, если класс имеет один набор зависимостей, которые являются неотъемлемой частью типа и никогда не изменятся, он может не иметь смысла поместить его в контейнер.
Регистрация типов, требующих внедрения зависимостей, должна выполняться в одном методе в приложении. Этот метод следует вызвать в начале жизненного цикла приложения, чтобы убедиться, что он знает о зависимостях между его классами. Приложение eShop с несколькими платформами выполняет этот MauiProgram.CreateMauiApp
метод. В следующем примере кода показано, как приложение с несколькими платформами eShop объявляет класс CreateMauiApp
MauiProgram
:
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
=> MauiApp.CreateBuilder()
.UseMauiApp<App>()
// Omitted for brevity
.RegisterAppServices()
.RegisterViewModels()
.RegisterViews()
.Build();
}
Метод MauiApp.CreateBuilder
создает MauiAppBuilder
объект, который можно использовать для регистрации зависимостей. Многие зависимости в мультиплатформенных приложениях eShop должны быть зарегистрированы, поэтому методы RegisterAppServices
RegisterViewModels
расширения и RegisterViews
созданы для предоставления упорядоченного и поддерживаемого рабочего процесса регистрации. В следующем примере кода показан метод RegisterViewModels
:
public static MauiAppBuilder RegisterViewModels(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddSingleton<ViewModels.MainViewModel>();
mauiAppBuilder.Services.AddSingleton<ViewModels.LoginViewModel>();
mauiAppBuilder.Services.AddSingleton<ViewModels.BasketViewModel>();
mauiAppBuilder.Services.AddSingleton<ViewModels.CatalogViewModel>();
mauiAppBuilder.Services.AddSingleton<ViewModels.ProfileViewModel>();
mauiAppBuilder.Services.AddTransient<ViewModels.CheckoutViewModel>();
mauiAppBuilder.Services.AddTransient<ViewModels.OrderDetailViewModel>();
mauiAppBuilder.Services.AddTransient<ViewModels.SettingsViewModel>();
mauiAppBuilder.Services.AddTransient<ViewModels.CampaignViewModel>();
mauiAppBuilder.Services.AddTransient<ViewModels.CampaignDetailsViewModel>();
return mauiAppBuilder;
}
Этот метод получает экземпляр MauiAppBuilder
, и мы можем использовать Services
свойство для регистрации моделей представления. В зависимости от потребностей приложения может потребоваться добавить службы с разными временем существования. В следующей таблице приведены сведения о том, когда может потребоваться выбрать эти различные сроки существования регистрации:
Метод | Description |
---|---|
AddSingleton<T> |
Будет создан один экземпляр объекта, который будет оставаться в течение всего времени существования приложения. |
AddTransient<T> |
При запросе во время разрешения будет создан новый экземпляр объекта. Временные объекты не имеют предопределенного времени существования, но обычно следуют времени существования их узла. |
Примечание.
Модели представления не наследуются от интерфейса, поэтому им нужен только конкретный тип, предоставленный AddSingleton<T>
для и AddTransient<T>
методов.
Он CatalogViewModel
используется рядом с корнем приложения и всегда должен быть доступен, поэтому регистрация в ней AddSingleton<T>
полезна. Другие модели представления, такие как CheckoutViewModel
и OrderDetailViewModel
ситуации, переходят или используются позже в приложении. Предположим, вы знаете, что у вас есть компонент, который не всегда может использоваться. В этом случае, если это память или вычислительные ресурсы или требуется JIT-данные, это может быть лучшим кандидатом на AddTransient<T>
регистрацию.
Другой распространенный способ добавления служб — использовать AddSingleton<TService, TImplementation>
методы и AddTransient<TService, TImplementation>
методы. Эти методы принимают два типа входных данных: определение интерфейса и конкретную реализацию. Этот тип регистрации лучше всего подходит для случаев, когда вы реализуете службы на основе интерфейсов. В приведенном SettingsService
ниже примере кода мы регистрируем наш ISettingsService
интерфейс с помощью реализации:
public static MauiAppBuilder RegisterAppServices(this MauiAppBuilder mauiAppBuilder)
{
mauiAppBuilder.Services.AddSingleton<ISettingsService, SettingsService>();
// Omitted for brevity...
}
После регистрации MauiAppBuilder.Build
всех служб необходимо вызвать метод, чтобы создать наш MauiApp
и заполнить контейнер внедрения зависимостей всеми зарегистрированными службами.
Внимание
Build
После вызова метода контейнер внедрения зависимостей неизменяем и больше не может быть обновлен или изменен. Убедитесь, что все службы, необходимые в приложении, были зарегистрированы перед вызовом Build
.
Разрешение
После регистрации типа его можно разрешить или внедрить как зависимость. При разрешении типа и контейнеру необходимо создать новый экземпляр, он внедряет все зависимости в экземпляр.
Как правило, при разрешении типа происходит одна из трех вещей:
- Если тип не зарегистрирован, контейнер создает исключение.
- Если тип зарегистрирован в качестве одного, контейнер возвращает одноэлементный экземпляр. Если этот тип вызывается при первом вызове, контейнер создает его при необходимости и сохраняет ссылку на него.
- Если тип зарегистрирован как временный, контейнер возвращает новый экземпляр и не сохраняет ссылку на него.
.NET MAUI предлагает ряд способов разрешения зарегистрированных компонентов в зависимости от ваших потребностей. Самым прямым способом получения доступа к контейнеру внедрения зависимостей является использование Element
Handler.MauiContext.Services
. Такой пример приведен далее:
var settingsService = this.Handler.MauiContext.Services.GetServices<ISettingsService>();
Это может быть полезно, если необходимо разрешить службу изнутри Element
или вне конструктора.Element
Внимание
Существует вероятность того, что свойство вашего Element
значения может иметь значение NULL, поэтому помните, что Handler
может потребоваться справиться с этими ситуациями. Дополнительные сведения см . в разделе "Жизненный цикл обработчика" в Центре документации Майкрософт.
При использовании Shell
элемента управления для .NET MAUIон неявно вызовет контейнер внедрения зависимостей для создания объектов во время навигации. При настройке нашего Shell
элемента управления Routing.RegisterRoute
метод привязывает путь маршрута к View
следующему примеру:
Routing.RegisterRoute("Filter", typeof(FiltersView));
Во время Shell
навигации он будет искать регистрации FiltersView
элементов и при обнаружении, он создаст это представление и введет все зависимости в конструктор. Как показано в приведенном ниже примере кода, CatalogViewModel
он будет внедрен в FiltersView
:
namespace eShop.Views;
public partial class FiltersView : ContentPage
{
public FiltersView(CatalogViewModel viewModel)
{
BindingContext = viewModel;
InitializeComponent();
}
}
Совет
Контейнер внедрения зависимостей отлично подходит для создания экземпляров модели представления. Если модель представления имеет зависимости, она будет обрабатывать создание и внедрение всех необходимых служб. Обязательно зарегистрируйте модели представления и все зависимости, которые они могут иметь с CreateMauiApp
помощью метода в MauiProgram
классе.
Итоги
Внедрение зависимостей позволяет разбиение конкретных типов из кода, зависящее от этих типов. Обычно он использует контейнер, содержащий список регистраций и сопоставлений между интерфейсами и абстрактными типами, а также конкретные типы, реализующие или расширяющие эти типы.
Microsoft.Extensions.DependencyInjection
упрощает создание слабо связанных приложений и предоставляет все функции, часто найденные в контейнерах внедрения зависимостей, включая методы для регистрации сопоставлений типов и экземпляров объектов, разрешения объектов, управления временем существования объектов и внедрения зависимых объектов в конструкторы объектов, которые он разрешает.