MVVMモデルとは? より効率的にシステムを開発するためのソフトウェアアーキテクチャについてご紹介

整列されたパソコンモニターの絵 みなさまこんにちは、おさかなです。今回の記事では、ソフトウェアアーキテクチャのひとつであるMVVMモデルについて、その仕組みや考え方を、関連するMVCモデルも含めて紹介したいと思います。ソフトウェアアーキテクチャを意識することで、記述するコードが規則によって分けられることで格段に読みやすくなり、保守も容易になります。本記事では、これからソフトウェアアーキテクチャを学ぼうとしている方、興味がある方にとって理解の一助になれば幸いです。

ソフトウェアアーキテクチャとは

そもそも「ソフトウェアアーキテクチャ」とは何でしょうか?ソフトウェアアーキテクチャは「ソフトウェアの構築方法や設計」のことで、ソフトウェア全体の設計方法を抽象的に表現したものです。ソフトウェア開発はアーキテクチャを意識しなくても行うことは可能です。しかし、開発の初期段階でソフトウェアアーキテクチャを考えておくことで、以下の観点でメリットがあります。

  • システムの理解・管理の容易化

システム全体の構造と機能が明確になるため、開発者や管理者がシステムを理解しやすくなります。

  • 開発効率の向上

アーキテクチャが定まっていると、開発するコンポーネントの役割が明確になるため開発がスムーズになります。

  • スケーラビリティの向上

コンポーネントが明確に分けられているため、システムの機能追加や拡張の場合でも柔軟に対応ができます。

  • テストの容易化

アーキテクチャではコンポーネントやモジュールで分割されているため、モジュールごとに個別にテストすることが可能となりテスト設計もやりやすくなります。

ソフトウェアアーキテクチャのメリット
ソフトウェアアーキテクチャのメリット

建物の内部構造(アーキテクチャ)を事前によく考えて設計することで良い建物が建てられるのと同様に、ソフトウェアでもアーキテクチャ設計を行うことで見通しの良いソフトウェアを開発することができます。ソフトウェアには非常に多くの種類が存在しますが、本記事では主にUIの開発で使用されるMVCモデル・MVVMモデルについて紹介したいと思います。

MVCモデルとは?

MVVMモデルについて紹介する前に、MVVMモデルとよく似た構造を持つMVCモデルについて紹介します。MVCモデルはModel-View-Controllerを略したアーキテクチャのことを言います。アプリケーション全体を役割によってModel、View、Controllerの3つのコンポーネントに分けて開発する手法です。それぞれのコンポーネントの特徴を以下に示します。

  • Model:アプリケーション内のデータ処理、保存、管理を行い、Viewに通知する。ビジネスロジックを担当する。

  • View:ModelからのデータをUIに表示する。

  • Controller:ユーザーからの入力を受け取り、ModelとView間のやり取りを制御する。

図にすると下図のように表されます。

MVCモデル
MVCモデル

文字の説明だけではイメージがつきにくいと思いますので、実際のC#のコードを例に紹介したいと思います。ここでは分かりやすいように、レストランの注文システムをMVCモデルに落とし込こんで考えてみましょう。以下のシナリオを想定します。

・客が端末などから注文したいメニューを登録する

・登録後、注文内容が裏で処理されて端末上に注文内容の一覧が表示される

このシナリオで登場するコンポーネントとしては注文内容を保持する「注文データ」、注文を管理する「注文管理システム」、新規に注文を行ったり注文内容を表示したりする「注文システム」とします。これをMVCモデルで実現する場合、下記のデータフローが考えられます。

  1. 客が注文を注文システムに入力。

  2. 入力された注文データが注文管理システムに送られる。

  3. 送られてきた注文データの内容をもとに現在の注文データを更新する。

  4. 更新した注文データを注文管理システムが読み取る。

  5. 更新後のデータを注文システムに設定する。

  6. 注文内容が注文システム上に表示される。

MVCモデルの例
MVCモデルの例

それではこの内容をコンポーネントの説明とともにコードに落とし込んでいきます。

注文データ(Model)

注文データには注文された内容を保持します。たとえば、注文内容や数量などです。これはデータの格納・保存を行っているためMVCモデルのModelに該当します。 注文データに料理のID、料理名、数量のデータを保持しているとして、下記のように書くことができます。

public class Order
{
    public int Id { get; set; }
    public string DishName { get; set; }
    public int Quantity { get; set; }

    public Order(int id, string dishName, int quantity)
    {
        Id = id;
        DishName = dishName;
        Quantity = quantity;
    }
}

注文システム(View)

注文システムには注文した料理が表示されており、客は注文システム経由で新規注文することができます。つまり、注文システムはユーザーに表示される部分であるためMVCモデルのViewに相当します。メニューには料理のIDと料理名、数量が表示されているとするとxamlで下記のように書けます。

<Window x:Class="RestaurantApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Order List" Height="300" Width="400">
    <Grid>
        <StackPanel>
            <ListBox ItemsSource="{Binding Orders}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Id}" Width="50"/>
                            <TextBlock Text="{Binding DishName}" Width="150"/>
                            <TextBlock Text="{Binding Quantity}" Width="50"/>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
            <TextBox x:Name="DishNameTextBox" />
            <TextBox x:Name="QuantityTextBox" />
            <Button Content="Add Order" Click="AddOrderButton_Click" />
        </StackPanel>
    </Grid>
</Window>

注文管理システム(Controller)

注文管理システムは注文システムから注文情報を受け取り、注文として起票します。つまり、注文データ(Model)とメニュー(View)を連携させる役割を果たします。これがMVCモデルにおけるControllerに該当します。 3つの注文があったときにそのデータを処理するコードは下記のように書くことができます。新規注文は画面のボタンが押されたときに登録イベントが発生するようにしています。

public partial class MainWindow : Window
{
    private ObservableCollection<Order> Orders;

    public MainWindow()
    {
        InitializeComponent();
        Orders = new ObservableCollection<Order>
        {
            new Order(1, "Pizza", 2),
            new Order(2, "Pasta", 1),
            new Order(3, "Salad", 3)
        };
        this.DataContext = this;
    }

    public ObservableCollection<Order> Orders
    {
        get { return Orders; }
        set { Orders = value; }
    }

    private void AddOrderButton_Click(object sender, RoutedEventArgs e)
    {
        int id = Orders.Count + 1;
        string dishName = DishNameTextBox.Text;
        int quantity;
        if (int.TryParse(QuantityTextBox.Text, out quantity))
        {
            Orders.Add(new Order(id, dishName, quantity));
        }
    }
}

これでMVCモデルを使用してレストランの注文システムを表現することができました。MVCモデルを使うことで、画面表示部分、データ処理部分、データ保持部分などの機能がコンポーネントごとに明確に分離され、システム全体の管理が容易になります。たとえば、システムを拡張して、注文後に削除できるようにしようとすると、注文管理システムであるControllerに対して削除するメソッドを追加すれば実現できることが分かります。

MVVMモデルとは?

それでは次にMVVMモデルについて紹介します。MVVMモデルもMVCモデルと同様にアプリケーションの構造を3つの主要なコンポーネントに分割します。MVVMモデルはModel-View-ViewModelを略したアーキテクチャです。

MVVMはクライアントサイドでの動的インタラクションやリアルタイムデータの更新が必要なアプリケーションに適しています。それぞれのコンポーネントの役割は下記のようになっています。

  • Model:データの加工、取得、保存を行う。

  • View:ViewModelの情報を使用してUIに表示。

  • ViewModel:ModelからUIに描画するための情報を変換保持する。

MVVMモデル
MVVMモデル

MVVMではViewとViewModel間の「双方向データバインディング」という仕組みがあります。これは、ViewModelのデータが更新されると関連するUIの要素が自動で反映されて画面上に表示されるという仕組みです。MVCではControllerがViewに対して指示を出して画面更新を行っていたのですが、MVVMではその必要はありません。そのため、MVVMモデルはリアルタイムでのデータ変更が必要なアプリケーションでもよく利用されるアーキテクチャとなっています。

MVVMモデルに関しても具体的なコードをもとに紹介します。 なお、Visual StudioでC#のWPFを用いてMVVMモデル開発を行う場合には拡張機能として「Prism Template Pack」と呼ばれるフレームワークを使用して開発することができます。今回はそれを使用したコードを紹介します。 シナリオとしてはMVCモデルの時と同様のものを考えます。登場するコンポーネントについても先ほどと同様に「注文データ」「注文システム」「注文管理システム」の3つですが、今回は注文管理システムがViewModelの役割を担います。具体的なデータフローは下記のようになります。

  1. 客が注文を注文システムに入力。
  2. 注文管理システムはバインディングした注文システム上のデータを取得。
  3. 受け取ったデータで注文データを更新する。
  4. 更新した注文データを注文管理システムが読み取る。
  5. 読み取った内容をバインディングにより注文システムに反映。
  6. 注文内容が注文システム上に表示される。

MVVMモデルの例
MVVMモデルの例

それでは今回もコンポーネントの説明とともにコードに落とし込んで説明していきます。

注文データ(Model)

注文データにはMVCモデルの時と同様に注文された内容を保持します。 これはデータの格納・保存を行っているためMVVMモデルのModelに該当します。 注文データに料理のID、料理名、数量のデータを保持しているとして、下記のように書くことができます。

public class Order
{
    public int Id { get; set; }
    public string DishName { get; set; }
    public int Quantity { get; set; }

    public Order(int id, string dishName, int quantity)
    {
        Id = id;
        DishName = dishName;
        Quantity = quantity;
    }
}

注文システム(View)

注文システムには注文した料理が表示されており、客は注文システム経由で新規注文することができます。これもMVCモデルのときと同じように、注文システムはユーザーに表示される部分であるためMVVMモデルのViewに相当します。MVCモデルのときと異なる部分として、注文データを追加するときにTextBoxタグに入力する料理名(DishName)と数量(Quantity)にバインディングを使用してViewModelと紐づけている箇所が挙げられます。

<Window x:Class="RestaurantApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Order List" Height="300" Width="400">
    <Grid>
        <StackPanel>
            <ListBox ItemsSource="{Binding Orders}">
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Id}" Width="50"/>
                            <TextBlock Text="{Binding DishName}" Width="150"/>
                            <TextBlock Text="{Binding Quantity}" Width="50"/>
                        </StackPanel>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
            <TextBox Text="{Binding NewOrder.DishName, UpdateSourceTrigger=PropertyChanged}" />
            <TextBox Text="{Binding NewOrder.Quantity, UpdateSourceTrigger=PropertyChanged}" />
            <Button Content="Add Order" Command="{Binding AddOrderCommand}" />
        </StackPanel>
    </Grid>
</Window>

注文管理システム(ViewModel)

注文管理システムは客が注文システムに入力した注文情報を即座に受け取り、注文として起票します。つまり、バインディングにより注文データ(Model)の情報を受け取り、その処理結果を注文システム(View)に表示する役割を果たします。これがMVVMモデルにおけるViewModelに該当します。 3つの注文があったときにそのデータを処理するコードは下記のように書くことができます。なおPrismでは、BindableBaseを継承してプロパティ変更通知を実装します。

public class MainWindowViewModel : BindableBase
{
    private ObservableCollection<Order> _orders;
    public ObservableCollection<Order> Orders
    {
        get { return _orders; }
        set { SetProperty(ref _orders, value); }
    }

    private Order _newOrder;
    public Order NewOrder
    {
        get { return _newOrder; }
        set { SetProperty(ref _newOrder, value); }
    }

    public ICommand AddOrderCommand { get; private set; }

    public MainWindowViewModel()
    {
        Orders = new ObservableCollection<Order>
        {
            new Order(1, "Pizza", 2),
            new Order(2, "Pasta", 1),
            new Order(3, "Salad", 3)
        };
        NewOrder = new Order(0, string.Empty, 0);
        AddOrderCommand = new DelegateCommand(AddOrder);
    }

    private void AddOrder()
    {
        int id = Orders.Count + 1;
        Orders.Add(new Order(id, NewOrder.DishName, NewOrder.Quantity));
        NewOrder = new Order(0, string.Empty, 0);
    }
}

NewOrderがView側でバインドされているため、ViewModel側ではTextBoxの内部の値を参照してデータ処理を行うコードは不要になります。また、逆に注文内容をNewOrderに代入するとそれがView側でリアルタイムに表示されるようになっています。

MVCモデルとMVVMモデルの特徴

今回のコード例ではMVCモデルとMVVMモデルは比較的よく似たコードになりましたが、下記の観点でMVCモデルとMVVMモデルの間で大きな違いがあります。

  • 変更されたデータの取得・処理の方法:

MVCモデル: ControllerがModelとViewの値を直接操作しています。また、Viewに表示する内容の更新はControllerが行います。

MVVM:モデル:ViewModelがModelとViewの間でデータバインディングを使用することでデータ連携を行っています。

  • 役割の分離:

MVCモデル: Controllerが中心的な役割を果たし、ユーザーの入力を処理し、ModelとViewを連携させています。

MVVMモデル: ViewModelが中心的な役割を果たし、ModelのデータをViewに反映させ、Viewの状態を管理しています。

本記事ではMVCモデルとMVVMモデルの考え方を具体的なコードを示して紹介しましたが、ソフトウェアアーキテクチャはあくまでも設計の考え方で、それ自体に優劣はなく、要件に応じて適切なアーキテクチャを選んで使用することが重要です。 そこで最後にMVCモデルとMVVMモデルのアーキテクチャを使用するうえでのメリットとデメリットを下表にまとめます。

各モデルのメリット・デメリット
各モデルのメリット・デメリット

まとめ

今回はソフトウェアアーキテクチャとしてMVCモデルとMVVMモデルを紹介しました。アーキテクチャを意識して開発することで、開発チームの認識共有や効率的な開発にもつながるので非常に有用です。ソフトウェアアーキテクチャは他にも非常に多くの種類があり、知っておくと開発の幅が広がるので興味があればぜひ調べてみてください。それではまた次回の記事でお会いしましょう!