Ioc (制御の反転)

MVVM パターンを使用するアプリケーションのコードベースでモジュール性を高めるために使用できる一般的なパターンは、何らかの形の制御の反転を使用することです。 特に最も一般的な解決策の 1 つは、バックエンド クラスに挿入される (つまり、viewmodel コンストラクターにパラメーターとして渡される) 多数のサービスの作成で構成される依存関係の挿入を使用することです。これにより、これらのサービスを使用するコードは、これらのサービスの実装の詳細に依存する必要がなくなります。また、これらのサービスの具体的な実装を簡単に入れ替えることができます。 また、このパターンを使用すると、プラットフォーム固有の機能を、必要な場所に挿入されるサービスを通して抽象化することで、バックエンドのコードで簡単に使用できるようにします。

MVVM Toolkit には、このパターンの使用を容易にする組み込みの API は提供されていません。なぜなら、Microsoft.Extensions.DependencyInjection パッケージのような専用のライブラリが既に存在するためです。これは、API の完全な機能と強力な DI セットを提供し、簡単にセットアップして使用する 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 プロパティが初期化され、すべてのアプリケーション サービスとビューモデルが登録されます。 また、アプリケーション内の他のビューから Services プロパティに簡単にアクセスするために使用できる新しい Current プロパティもあります。 次に例を示します。

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...
}

ここでは、IFileLogger インターフェイスを実装し、IFilesServiceIConsoleService インスタンスを要求する FileLogger 型があります。 コンストラクターの挿入とは、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 抽象型のコンストラクターを呼び出して、返されるインスタンスを取得します。

ビューモデルについてはどうですか?

サービス プロバイダーの名前には「サービス」が含まれていますが、実際にはビューモデルを含むあらゆるクラスのインスタンスを解決するために使用できます。 コンストラクターの挿入など、上記で説明したものと同じ概念が引き続き適用されます。 コンストラクターを介して IContactsServiceIPhoneService インスタンスを使用している ContactsViewModel 型があるとします。 次のような 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 の詳細については、こちらを参照してください。

  • サンプル アプリ (複数の UI フレームワーク向け) を確認して、MVVM Toolkit の実際の動作を確認してください。
  • 単体テストで、その他の例を確認することもできます。