[C#/WPF/MVVM]今さらMVVMについて調べた

# はじめに
恥ずかしながら未だにWinForms案件ばかりでWPF、ましてや**MVVM**なんて使ったことがない時代遅れなプログラマーです。恥ずかしい・・・

**そんな私の元にも遂にWPF案件が舞い降りてきてしまった。**
ここ数日「WPFでどうやって作るねん」から調べ始めて、今さら**MVVM**という存在を知り、「**MVVM**ってなんやねん」をずっと調べているけど・・・

**なんだかよく分からない！**

「`View`-`ViewModel`-`Model`に分けて作りましょう」
「テストしやすくなるよ」
とかざっくりとは分かる。
でも具体的なその分け方が調べていても人によって微妙に違って何が正解か分からない。[^1]
そもそも正解なんて無いのかもしれない。

で、考え込みすぎて訳が分からなくなってきたので、自分なりの考えを整理するためにも、**MVVM**について書いてみることに。

[^1]: 理解力が足りないだけかもしれない。

**WinForms脳な人間がMVVMについて数日調べただけの内容なので間違えてる部分が多々あるかもしれません。その時は優しく教えてくれると嬉しいです。優しく・・・** 

# なんでMVVMに分けるのだろう？
`V-VM-M`の分け方について調べているだけだと明確な物が見えてこないので、視点を変えて
**「なんでMVVMに分けるのだろう？」**
という目的から考えてみることにした。

## 目的
### テスタビリティ(テスト容易性)向上
画面とコードがくっついてるとユニットテストがし辛い。
そこで画面(View)とコード(ViewModel/Model)に分離してやるとやりやすくなる。

なるほど、たしかに。

### プロダクティヴィティ(生産性)向上
画面(View)とコード(ViewModel/Model)が分離してると、デザイナは画面(View)、プログラマはコード(ViewModel/Model)と明確に分業できる。

もっともウチにはデザイナなんていない。

### ポータビリティ(移植性)向上（？）
画面(View)とコード(ViewModel/Model)が分離してると、画面(View)を用意するだけで他のプラットフォームに対応できる・・・かもしれない。
と言うのも、これについて触れられている情報を殆ど見かけなくて、あくまで勝手なイメージ。
ただ理論上は可能なはず・・・[^2]

[^2]: Xamarinを使えば出来たりしないかな？（Xamarin未経験）

## 分け方が曖昧な原因はここにある？
恐らく大まかにこの３つが目的で、特にテスタビリティ向上が重要っぽい。
**あれ？これらの目的って、とりあえず画面さえ分離しちゃえば一応は満たせる？**
そのせいで`V-VM-M`の分け方が曖昧なんだろうか。

# 理想的なプロジェクト構成を考えてみる
↑に挙げた目的を元に、まずはどんなプロジェクト構成が理想的かを大まかに考えてみる。

## こんな感じ？
```
- MvvmTest (.Net Core/WPF)
    - Views
        - MainView.xaml
            - MainView.xaml.cs
    - App.xaml
        - App.xaml.cs
    - AssemblyInfo.cs
- MvvmTest.MVM (.Net Standard)
    - MainViewModel.cs
    - MainModel.cs
```
あくまで最小の基本構成。DIコンテナとか画面遷移とか考え出すと多分これじゃあ足りない。

### MvvmTest プロジェクト
Viewとアプリケーションを定義・実装するプロジェクト。
Viewを`MvvmTest.Views`プロジェクトに分けるのも有りかも。意味があるかは謎。
あとこれを`MvvmTest.NetCore`にして、Xamarinで`MvvmTest.iOS`とか作れると幸せ。

### MvvmTest.MVM プロジェクト
ViewModel と Model を定義・実装するプロジェクト。
`.Net Standard`にしたい！！[^3]
MVM・・・もう少し良い名前はないものか。
`MvvmTest.ViewModels`と`MvvmTest.Models`に分けるのも有りかも。意味があるかは謎。
ViewModel と Model は同じ階層で近い位置にいた方が便利のような？

[^3]: .Net Standard大好き

# MVVMそれぞれの役割を考えてみる
次に`View`、`ViewModel`、`Model`それぞれの役割を大まかに考えてみる。

## View
画面。それ以上でもそれ以下でもない。
`ViewModel`が提供するプロパティに沿って画面を定義する。
デザインに関すること(XAML)以外は書きたくない。

## Model
ロジック。やるべき処理をやる人。
`ViewModel`からパラメータを貰って処理を行う。
どんなデザインの画面かは知らない。[^4]

[^4]: 何をする画面かは知ってる。じゃないと何の処理をすれば良いのか分からない。あくまでデザインを知らないだけ。

## ViewModel
画面とロジックを結ぶ橋渡し役。
画面からどんな情報が欲しいか定義して、ロジックの求める形で情報を渡して、ロジックから結果を貰って、結果を画面に渡してあげる。
大体どんな画面か知っていて[^5]、画面寄りの処理（入力チェックとか）はこの人がやるべき？（不明瞭）

[^5]: このパラメータはテキストボックス的なやつで、このパラメータはチェックボックス的なやつで・・・みたいな。

# それぞれ持つべきものを考えてみる
↑の役割をふまえて、`View`、`ViewModel`、`Model`がそれぞれ持つべきもの（やるべきこと）を大まかに考えてみる。

## View
* 画面デザイン
* 対応する`ViewModel`のインスタンス（`View`は`ViewModel`に依存、DataContext）
* コントロールが`ViewModel`のどのプロパティと連動するか（Binding）

## ViewModel
* 対応する`Model`のインスタンス（`ViewModel`は`Model`に依存）
* 画面の入力データを受け取る[^6]
* 入力データが`Model`に渡せるかどうかの入力チェック[^7]
* エラーを`View`に通知（INotifyDataErrorInfo）
* ボタンなどのイベント処理（ICommand）
* 入力データを`Model`の求める形に変換して渡す
* `Model`のプロパティ変更通知を受け取る
* プロパティ変更を`View`に通知（INotifyPropertyChanged）

## Model
* `ViewModel`からパラメータを受け取る
* パラメータのエラーチェック
* エラーを`ViewModel`に通知（INotifyDataErrorInfo）
* 実際にやりたい処理
* 結果をプロパティに格納
* プロパティ変更を`View`に通知（INotifyPropertyChanged）

[^6]: `View`がバインディングするところでエラーが出ないようにするべき？（数値入力前提のテキストボックスでも、とりあえずstring型のプロパティで受け取るとか）
[^7]: `Model`の処理に依存するエラーは知らない。あくまで「渡せるかどうか」だけ。

# 実際に書いてみる
今まで考えたことをふまえて、実際にコードを書いてみる。
数値を２つを渡して実行すると足し算した結果が返ってくる超単純なアプリを**MVVM**で作ってみた。

## Model(MainModel.cs)
まずは画面は気にせずやるべき処理を書いてみる。

```csharp
class MainModel : ViewModelBase, IMainModel
{
	private Property<int> ans = new Property<int>();

	public int ParamA { get; set; }
	public int ParamB { get; set; }

	public int Answer
	{
		get => this.ans;
		set => this.ans.SetValue(value, this);
	}

	public void Sum()
		=> this.Answer = this.ParamA + this.ParamB;
}
```
２つの数字を受け取って足し算するだけ。ややこしいのでパラメータエラーはなし。
因みに`ViewModelBase`クラスにプロパティ変更通知が実装されていて、`Property<T>`の`SetValue`で良い感じに通知してる。
`IMainModel`は、`MainModel`の定義そのまま。

## ViewModel(MainViewModel.cs)
次に画面を想像しつつ`Model`と繋ぐ`ViewModel`を書いてみる。
数字を入力するテキストボックス２個(ParamA,ParamB)と、実行ボタン１個(SumCommand)と、結果を表示するラベルが１個(Answer)かな。

```csharp
public class MainViewModel : ViewModelBase
{
	private IMainModel Model { get; }

	private Property<string> paramA = new Property<string>("0",
		(value) =>
		{
			if (value.Length == 0)
				return "未入力エラー";
			else if (!int.TryParse(value, out int _))
				return "フォーマットエラー";
			else
				return null;
		});

	private Property<string> paramB = new Property<string>("0",
		(value) =>
		{
			if (value.Length == 0)
				return "未入力エラー";
			else if (!int.TryParse(value, out int _))
				return "フォーマットエラー";
			else
				return null;
		});

	private Property<int> ans = new Property<int>();

	public string ParamA
	{
		get => this.paramA;
		set
		{
			// 入力エラーがなければModelに設定
			if (this.paramA.SetValue(value, this))
				this.Model.ParamA = int.Parse(this.ParamA);
		}
	}

	public string ParamB
	{
		get => this.paramB;
		set
		{
			// 入力エラーがなければModelに設定
			if (this.paramB.SetValue(value, this))
				this.Model.ParamB = int.Parse(this.ParamB);
		}
	}

	public int Answer
	{
		get => this.ans;
		set => this.ans.SetValue(value, this);
	}

	public ICommand SumCommand { get; }

	public MainViewModel()
	{
		// TODO: 本来ならDIコンテナから取得
		this.Model = new MainModel();
		this.Model.PropertyChanged += Model_PropertyChanged;

		// Sumボタン
		this.SumCommand = new Command(() =>
		{
			// 実行
			this.Model.Sum();
		}, paramA, paramB);
	}

	private void Model_PropertyChanged(object sender, PropertyChangedEventArgs e)
	{
		if (e.PropertyName == nameof(this.Model.Answer))
			this.Answer = this.Model.Answer;
	}
}
```
`View`からデータを受け取って、`Model`に渡せるデータかチェックして、SumCommandの有効・無効を制御。
SumCommandの処理で`Model`にパラメータを渡して実行。
`Model`のプロパティ変更通知で結果を取り出して`ViewModel`に通知。

独自のクラスが多くて分かりにくい・・・
ひとまず`Property<T>`には検証機能もあって、`Command`クラスには指定した`Property<T>`のエラー状態と連動して有効・無効が勝手に切り替わる・・・くらいのイメージで・・・[^8]

[^8]: `INotifyPropertyChanged`や`INotifyDataErrorInfo`はどういう実装が便利だろう？とお試しで作ったクラス。実際にこれで良いかは分からない。（全容はGitHubで・・・）

## View(MainView.xaml)
最後に画面。
`ViewModel`に従って数字を入力するテキストボックス２個と、実行ボタン１個と、結果を表示するラベルが１個。

```xml
<Window x:Class="MvvmTest.Views.MainView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MvvmTest.Views"
		xmlns:vm="clr-namespace:MvvmTest.VM;assembly=MvvmTest.VM"
        mc:Ignorable="d"
        Title="Main" SizeToContent="WidthAndHeight">

	<Window.DataContext>
		<vm:MainViewModel />
	</Window.DataContext>
	
	<Grid MinWidth="300" MinHeight="200">
		<Grid.ColumnDefinitions>
			<ColumnDefinition Width="100" />
			<ColumnDefinition/>
		</Grid.ColumnDefinitions>
		<Grid.RowDefinitions>
			<RowDefinition/>
			<RowDefinition/>
			<RowDefinition/>
			<RowDefinition/>
		</Grid.RowDefinitions>

		<!-- ParamA -->
		<TextBlock Grid.Row="0" Grid.Column="0" Margin="0,0,10,0"  HorizontalAlignment="Right" VerticalAlignment="Center" Text="ParamA"/>
		<TextBox Grid.Row="0" Grid.Column="1" MaxHeight="24" Margin="10, 0, 10, 0" VerticalContentAlignment="Center" Text="{Binding ParamA, UpdateSourceTrigger=PropertyChanged}" />
		<!-- ParamB -->
		<TextBlock Grid.Row="1" Grid.Column="0" Margin="0,0,10,0" HorizontalAlignment="Right" VerticalAlignment="Center" Text="ParamB"/>
		<TextBox Grid.Row="1" Grid.Column="1" MaxHeight="24" Margin="10, 0, 10, 0" VerticalContentAlignment="Center" Text="{Binding ParamB, UpdateSourceTrigger=PropertyChanged}" />
		<!-- Button -->
		<Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Margin="10,10,10,10" Content="Sum" Command="{Binding SumCommand}"/>
		<!-- Answer  -->
		<TextBlock Grid.Row="3" Grid.Column="0" Margin="0,0,10,0" HorizontalAlignment="Right" VerticalAlignment="Center" Text="Answer"/>
		<TextBlock Grid.Row="3" Grid.Column="1" HorizontalAlignment="Left" Margin="10, 0, 10, 0" VerticalAlignment="Center" Text="{Binding Ans}" />

	</Grid>
</Window>
```
画面のデザインと、ViewModelが誰かと、コントロール毎のバインディングを定義するだけ。

### コードビハインド(MainView.xaml.cs)

```csharp
/// <summary>
/// MainView.xaml の相互作用ロジック
/// </summary>
public partial class MainView : Window
{
	public MainView()
	{
		InitializeComponent();
	}
}
```
生成されたコードそのまま。**絶対に触らない**（鉄の意志）

# ソースコードの全てはGitHubに上げてみた
[mitsu-at3/MvvmTest: Mvvm Test Project](https://github.com/mitsu-at3/MvvmTest)
仕事でGitLabは使っているけど、GitHubは初めて使った・・・（今さら）

# 合ってるだろうか？
色々と調べたりMVVMの目的を考えると、この分け方が１番しっくり来るけど、合ってるのか分からない・・・
特に`ViewModel`に定義する`View`からデータを受け取るためのプロパティの型を、例えば数値入力でもテキストボックスならstring型みたいに、`View`のコントロールの都合を考慮した型にするのが正しいのかどうかが謎。
調べても明確にそういう考え方してる情報に出会えなかった。

ただ`ViewModel`のプロパティをintにしてしまうと、`View`の段階で入力エラーが出てしまって、そのままだと`ViewModel`側でエラーを検知できないから、`View`から`ViewModel`に伝える仕組みが必要になってしまう。
でも`View`にはなるべく余計なものは書きたくない。
って考えると、`ViewModel`側はとりあえずstringで受け入れて、入力チェックは`ViewModel`側でやるのがベター？[^9]

[^9]: stringにしておけばどんな入力でも受け入れられるテキストボックスだからってだけで、どんな型にしてもエラーが発生しうるコントロールの場合は、`View`→`ViewModel`の通知は避けられない？

# 調べてる中で気になったこととか
最後に調べてるときに見かけた情報で「それ合ってるの？」と気になったことなど。
あちこちの情報をひたすら漁ってる中で見かけた情報なのでどこで見たのかは覚えてない・・・[^10]

[^10]: ただその情報のせいで余計に混乱した

## Modelがただのデータクラス

```csharp
public class PersonModel
{
	public string Name { get; set; }
	public int Age { get; set; }
}
```
Modelの意味を勘違いしてるのか、ロジックはこれから実装するつもりだったのか・・・

## 「ViewModelとModelは同じプロパティが並びます」
必ずしも同じ必要は無いのではなかろうか？
同じだと`ViewModel`が架け橋する意味も薄くなっちゃう。
`ViewModel`と`Model`の関係が崩れなければ、違っていても良い気がする。

## ViewModelが完全にModelへの受け渡しだけ
↑と似ているけど・・・

```csharp
public class PersonModelView
{
	public string Name
	{
		get => this.Model.Name;
		set => this.Model.Name = value;
	}
	public int Age
	{
		get => this.Model.Age;
		set => this.Model.Age= value;
	}

	// ・・・Modelから通知を受けたり、コマンドを受けてModelを実行したりだけ
}
```
入力チェックも何もない画面なら有りなのかもしれないけど、ここまで来るとVMとMを分ける意味がないような・・・
超小規模なプロジェクトなら`Model`なしも有り？

## あなたのモデルはどこから？

```csharp
	public MainViewModel(IMainModel model)
	{
		this.Model = model;
	}
```
なんやかんやの仕組みがあって自動的に依存性注入で渡ってくるならこれも良いと思う。

### でもこれは嫌だ(MainView.xaml.cs)

```csharp
/// <summary>
/// MainView.xaml の相互作用ロジック
/// </summary>
public partial class MainView : Window
{
	public MainView()
	{
		IMainModel model = DIコンテナ的なやつ.GetService(typeof(IMainModel));
		this.DataContext = new MainViewModel(model);
		InitializeComponent();
	}
}
```
**コードビハインドには書きたくない！！！**

### ViewModelのコンストラクタで作れば良くない？

```csharp
	public MainViewModel()
	{
		this.Model = DIコンテナ的なやつ.GetService(typeof(IMainModel));
	}
```
`ViewModel`が`Model`に依存するのは間違いじゃないので、素直にこっちの方が良いような。

## Modelがシングルトン
これが正しいのかが１番分からない。
今までの考えからすると、`View`-`ViewModel`-`Model`は１つのセットと見なして、生存期間は同じにしておく方が自然のような気がする。
シングルトンで扱いたい機能は、`Model`よりも下に定義して、`Model`の中でそのシングルトンクラスを使った方がMVVMの関係性が明確で分かりやすくない？

でも厳密には「`View`と`ViewModel`以外全てが`Model`」らしいから[^11]、シングルトンでも間違いではないのかな・・・
個人的には少なくとも`ViewModel`が参照する`Model`は、生存期間を同じにしておきたいなあ。

[^11]: 混乱の素

# おわりに
長々と書いていて「こう考えるのが正しいのではないか？」というのはところまでは来たけども、まだ確信を持って「この考えで正しい！」とは言えない・・・
しっくりは来てるんだけどなあ。
でも序盤に挙げた「目的」を明確に認識しておくだけでも「その形が間違ってるか？」の判断材料にはなりそう。
少しは最新の開発スタイルに近付けただろうか・・・？

あと画面遷移とかダイアログ表示とかDIコンテナとか調べだすと、Prismとか外部フレームワークに頼りたくなる理由が分かってきた・・・
何となく自力でも実装できそうな雰囲気だけど、かなり面倒そうな印象。

# おまけ：DIコンテナって・・・
MVVMについて調べる中で初めて「DIコンテナ」を知ったのだけど（今さら）、解説を読んだときに真っ先にあいつを思い出した・・・

```cpp
HRESULT CoCreateInstance(
  REFCLSID  rclsid,
  LPUNKNOWN pUnkOuter,
  DWORD     dwClsContext,
  REFIID    riid,
  LPVOID    *ppv
);
```
取得したいInterfaceの情報を渡すとインスタンスが返ってくるってこれじゃん。
そう思うと急に親しみが湧いてきたのでした。おしまい。