Поделиться через


Ioc (инверсия элемента управления)

Распространенный шаблон, который можно использовать для увеличения модульности в базе кода приложения с помощью шаблона MVM, заключается в использовании некоторой формы инверсии элемента управления. Одним из наиболее распространенных решений, в частности, является использование внедрения зависимостей, которое состоит в создании ряда служб, внедренных в внутренние классы (т. е. передаваемых в качестве параметров конструкторам viewmodel) — это позволяет коду использовать эти службы не полагаться на сведения о реализации этих служб, а также упрощает переключение конкретных реализаций этих служб. Этот шаблон также позволяет легко сделать функции, характерные для платформы, доступными для внутреннего кода, абстрагируя их через службу, которая затем внедряется по мере необходимости.

Набор средств MVVM не предоставляет встроенные API для упрощения использования этого шаблона, так как для этого пакета уже существуют выделенные библиотеки, такие как Microsoft.Extensions.DependencyInjection пакет, который предоставляет полный и мощный набор API и действует как простой для настройки и использования IServiceProvider. В следующем руководстве приведены примеры интеграции библиотеки в приложения с помощью шаблона MVVM.

API платформы: Ioc

Настройка и разрешение служб

Первым шагом является объявление экземпляра IServiceProvider и инициализация всех необходимых служб, как правило, при запуске. Например, в UWP (но аналогичную настройку также можно использовать в других платформах):

public sealed partial class App : Application
{
    public App()
    {
        Services = ConfigureServices();

        this.InitializeComponent();
    }

    /// <summary>
    /// Gets the current <see cref="App"/> instance in use
    /// </summary>
    public new static App Current => (App)Application.Current;

    /// <summary>
    /// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
    /// </summary>
    public IServiceProvider Services { get; }

    /// <summary>
    /// Configures the services for the application.
    /// </summary>
    private static IServiceProvider ConfigureServices()
    {
        var services = new ServiceCollection();

        services.AddSingleton<IFilesService, FilesService>();
        services.AddSingleton<ISettingsService, SettingsService>();
        services.AddSingleton<IClipboardService, ClipboardService>();
        services.AddSingleton<IShareService, ShareService>();
        services.AddSingleton<IEmailService, EmailService>();

        return services.BuildServiceProvider();
    }
}

Services Здесь свойство инициализируется при запуске, а все службы приложений и представления регистрируются. Существует также новое Current свойство, которое можно использовать для легкого Services доступа к свойству из других представлений в приложении. Например:

IFilesService filesService = App.Current.Services.GetService<IFilesService>();

// Use the files service here...

Ключевым аспектом здесь является то, что каждая служба может очень хорошо использовать API для конкретной платформы, но так как все они абстрагированы через интерфейс, который используется в коде, мы не должны беспокоиться о них всякий раз, когда мы просто разрешаем экземпляр и используем его для выполнения операций.

Внедрение конструктора

Одна из мощных функций, которая доступна, — "внедрение конструктора", что означает, что поставщик служб DI может автоматически разрешать косвенные зависимости между зарегистрированными службами при создании экземпляров запрашиваемого типа. Рассмотрим следующую службу:

public class FileLogger : IFileLogger
{
    private readonly IFilesService FileService;
    private readonly IConsoleService ConsoleService;

    public FileLogger(
        IFilesService fileService,
        IConsoleService consoleService)
    {
        FileService = fileService;
        ConsoleService = consoleService;
    }

    // Methods for the IFileLogger interface here...
}

Здесь у нас есть FileLogger тип, реализующий IFileLogger интерфейс, и требуется IFilesService и IConsoleService экземпляры. Внедрение конструктора означает, что поставщик услуг DI автоматически собирает все необходимые службы, как показано ниже.

/// <summary>
/// Configures the services for the application.
/// </summary>
private static IServiceProvider ConfigureServices()
{
    var services = new ServiceCollection();

    services.AddSingleton<IFilesService, FilesService>();
    services.AddSingleton<IConsoleService, ConsoleService>();
    services.AddSingleton<IFileLogger, FileLogger>();

    return services.BuildServiceProvider();
}

// Retrieve a logger service with constructor injection
IFileLogger fileLogger = App.Current.Services.GetService<IFileLogger>();

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

А как насчет viewmodels?

Поставщик услуг имеет "службу" в своем имени, но на самом деле его можно использовать для разрешения экземпляров любого класса, включая viewmodels! Те же понятия, описанные выше, по-прежнему применяются, включая внедрение конструктора. Представьте, что у нас был ContactsViewModel тип, использующий экземпляр с IPhoneService помощью IContactsService конструктора. У нас может быть ConfigureServices такой метод:

/// <summary>
/// Configures the services for the application.
/// </summary>
private static IServiceProvider ConfigureServices()
{
    var services = new ServiceCollection();

    // Services
    services.AddSingleton<IContactsService, ContactsService>();
    services.AddSingleton<IPhoneService, PhoneService>();

    // Viewmodels
    services.AddTransient<ContactsViewModel>();

    return services.BuildServiceProvider();
}

А затем в нашем ContactsViewприложении мы назначим контекст данных следующим образом:

public ContactsView()
{
    this.InitializeComponent();
    this.DataContext = App.Current.Services.GetService<ContactsViewModel>();
}

Дополнительные документы

Дополнительные сведения смMicrosoft.Extensions.DependencyInjection. здесь.

Примеры

  • Ознакомьтесь с примером приложения (для нескольких платформ пользовательского интерфейса), чтобы просмотреть набор средств MVVM в действии.
  • Дополнительные примеры можно найти в модульных тестах.