MVVM とは

完了

MVVM を使わない .NET MAUI アプリでは、一般に、その "分離コード" ファイル内のコードが多くなります。 .NET MAUI の分離コード ファイルは、"{何か}.xaml.cs" というパターンに従います。 分離コード ファイル内のほとんどのコードは、ユーザー インターフェイス (UI) の動作の制御に使われます。 "UI の動作" には、色やテキストの変更など、UI に "対して" 発生するあらゆるものが含まれます。 また、これには、ボタン クリックのハンドラーなど、UI が "原因となって" 発生するあらゆるものが含まれます。

この方法の問題の 1 つは、分離コード ファイルに対する単体テストを作成するのが難しいことです。 多くの場合、分離コード ファイルでは、XAML を解析して作成された、または他のページによって作成されたアプリケーション状態が仮定されます。 モバイル デバイス上で実行すらされていないかもしれない単体テスト ランナーではこのような条件を処理するのは難しく、ユーザー インターフェイスについてはさらに困難です。 そのため、これらのファイルにおける UI の動作を単体テストでテストできることはほとんどありません。

しかし、MVVM パターンが役立つ場所は次のとおりです。 MVVM パターンを正しく使用すると、UI の動作ロジックのほとんどを "ビューモデル" という単体テスト可能なクラスに移動することで、これらの問題を解決できます。 MVVM パターンは、データ バインディングをサポートするフレームワークで最もよく使われます。 それは、.NET MAUI では、各 UI 要素を viewmodel にデータ バインドして、ビューやコードビハインド内のコードを排除またはほとんど排除できるためです。

MVVM アプリケーションの各部分について

viewmodel は MVVM パターン独特の部分ですが、このパターンではモデル部分とビュー部分も定義されます。 これらの部分の定義は、Model-View-Controller (MVC) など、その他のいくつかの一般的なパターンと一致しています。

モデルとは何か

MVVM アプリケーションでは、モデルという用語を使用して、ビジネス データと操作を表します。 モデルは、アプリのユーザー プレゼンテーションに関与しません。

モデルに属するコードを決定するための便利なルールは、異なるプラットフォームとの間で移植可能である必要があるということです。 モバイル アプリから Web インターフェイス、またはコマンドライン プログラムまで、すべてのインスタンスで同じ "モデル" を使用します。 これは、ユーザーに情報を表示する方法とは関係ありません。

このシナリオから人事管理システムのアプリケーションについて考えた場合、モデルには Employee クラスと Department クラスを含めることができます。それぞれのエンティティに関するデータとロジックがそれらで保持されます。 モデルには、永続化ロジックを保持する EmployeeRepository クラスのようなものも含めることができます。 他のいくつかのソフトウェア設計パターンでは、"リポジトリ" のようなものがモデルとは別に考慮されます。 しかし、MVVM のコンテキストでは、多くの場合、ビジネス ロジックやビジネス データをモデルの一部と見なします。

C# での Model の例を次に 2 つ示します。

public class Employee
{
    public int Id { get; }
    public string Name { get; set; }
    public Employee Supervisor { get; set; }
    public DateTime HireDate { get; set; }
    public void ClockIn() { ... }
}

public class EmployeeRepository
{
    public IList<Employee> QueryEmployees() { ... }
    ...
}

ビューとは何か

"ビュー" コードは、ボタンや入力フィールドなどのコントロールや、テーマ、スタイル、フォントなどの純粋に視覚的な要素など、ユーザーと直接やり取りするものを制御します。

.NET MAUI では、ビューを自分で生成するために C# コードを記述する必要はありません。 代わりに、多くの場合、XAML ファイルでビューを定義します。 もちろん、コードを使用して独自のビューを作成するカスタム ユーザー コントロールを呼び出す状況もあります。

viewmodel とは

これで viewmodel に戻ってきました。 viewmodel は、ビジネス ロジック (モデル) とビュー (UI) の間の仲介役です。

モデルとビューの間の仲介としてのビューモデルを示す図。

HR アプリケーションの場合、viewmodel がどのように役立つかを考えてみましょう。 たとえば、従業員の利用可能な休暇時間を表示するビューがあり、休暇残高に "2 週間、3 日、4 時間" と示しているとします。しかし、モデルのビジネス ロジックでは、同じ値を 13.5 日として提供します。これは、8 時間の稼働日での合計日数を表す 10 進数です。 オブジェクト モデルは、次の一覧のようになります。

  • モデル – 次のメソッドを含む Employee クラス。

    public decimal GetVacationBalanceInDays()
    {
        //Some math that calculates current vacation balance
        ...
    }
    
  • ビューモデル – 次のようなプロパティを持つ EmployeeViewModel クラス。

    public class EmployeeViewModel
    {
        private Employee _model;
    
        public string FormattedVacationBalance
        {
            get
            {
                decimal vacationBalance = _model.GetVacationBalanceInDays();
                ... // Some logic to format and return the string as "X weeks, Y days, Z hours"
            }
        }
    }
    
  • ビュー – 1 つのラベルと閉じるボタンを含む XAML ページ。 このラベルには、viewmodel のプロパティに対するバインドが含まれます。

    <Label Text="{Binding FormattedVacationBalance}" />
    

    あとは、EmployeeViewModel のインスタンスに設定されたこのページ用の BindingContext が必要になるだけです。

この例では、モデルに "ビジネス ロジック" が含まれます。 このロジックは、ビジュアル ディスプレイまたはデバイスにはバインドされていません。 ハンドヘルド デバイスまたはデスクトップ コンピューターにも同じロジックを使用できます。 ビューは、ビジネス ロジックについては何も知りません。 ビュー コントロール (ラベルなど) は、画面上のテキストを取得する方法を知っていますが、休暇残高やランダムな文字列かどうかは気にしません。 viewmodel は、両方の世界を "少し" 理解しているので、仲介役として機能できます。

興味深い点は、viewmodel が仲介役を果たす方法です。それでは、ビューをバインドできるプロパティが公開されます。 パブリック プロパティは、viewmodel でデータを提供する唯一の方法です。 viewmodel と "呼ばれる" ようになったのは、このためです。 MVVM の "モデル" は、ビジネス プロセスの構造、データ、ロジックを表し、viewmodel は、ビューに必要な構造、データ、ロジックを表します。

viewmodel でのビューの動作方法

viewmodel とモデルのリレーションシップを見ると、それは標準のクラス間リレーションシップです。 viewmodel にはモデルのインスタンスがあり、プロパティを使用してモデルの側面をビューに公開します。 しかし、ビューは viewmodel のプロパティをどのように取得して設定するのでしょうか? viewmodel が変更されたとき、ビューはどのようにしてビジュアル コントロールを新しい値で更新するのでしょうか? 答えはデータ バインディングです。

viewmodel のプロパティは、オブジェクトがビューにバインドされるときに読み取られます。 しかし、バインドがビューに適用された後、viewmodel のプロパティが変更されたかどうかをバインドで識別する方法はありません。 viewmodel を変更しても、バインドを介してビューに新しい値が自動的に反映されることはありません。 viewmodel からビューに更新するには、viewmodelSystem.ComponentModel.INotifyPropertyChanged インターフェイスを実装する必要があります。

INotifyPropertyChanged インターフェイスは、PropertyChanged という名前の 1 つのイベントを宣言します。 それは、1 つのパラメーター (その値を変更したプロパティの名前) を受け取ります。 .NET MAUI で使用されるバインディング システムは、このインターフェイスを理解し、そのイベントをリッスンします。 viewmodel でプロパティが変更され、イベントが発生すると、バインドによってその変更がターゲットに通知されます。

従業員の viewmodel を使用した HR アプリケーションで、これがどのように動作するかについて考えてみましょう。 viewmodel には、従業員を表すプロパティ (従業員の名前など) があります。 viewmodelINotifyPropertyChanged インターフェイスを実装しており、Name プロパティが変更されると、次のような PropertyChanged イベントが発生します。

using System.ComponentModel;

public class EmployeeViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    private Employee _model;

    public string Name
    {
        get {...}
        set
        {
            _model.Name = value;
            OnPropertyChanged(nameof(Name))
        }
    }

    protected void OnPropertyChanged(string propertyName) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

従業員の詳細を説明するビューには、viewmodelName プロパティにバインドされたラベル コントロールが含まれています。

<Label Text="{Binding Name}" />

viewmodelName プロパティが変更されると、そのプロパティの名前で PropertyChanged イベントが発生します。 バインディングはイベントをリッスンし、Name プロパティが変更されたことをラベルに通知します。 その後、ラベルの Text プロパティが最新の値で更新されます。

自分の知識をチェックする

1.

開発者は .NET MAUI アプリケーションで作業しており、MVVM パターンを実装したいと考えています。 ビジネス データと操作のモデルと、ユーザー操作のためのビューを作成しました。 モデルとビューの間の仲介者として、次に何を作成する必要がありますか?

2.

チームは、.NET MAUI を使用してモバイル アプリケーションを開発しており、ビジネス ロジックが異なるプラットフォームとの間で移植可能であることを確認したいと考えています。 MVVM パターンでは、このビジネス ロジックをどこに配置する必要がありますか?