c++ で INotifyProperyChanged をどう実現するか?

c++/cx metro の場合 mvvm がどのように実装されているか | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3542

の続きで考察を30分ほど。

mvvm, mvc などの現在の実装に分け入ってしまうと混乱するので「view を分離する」という点に絞って実現してみる。
前回解説した通り、XAML 側には、文字列を Notation として受け取る仕組みがあるので、それを使うとすると、PropertyChangedEventHandler に沿って作る必要がある。これは実装れべるでそうなっているので、XAML を View として使う限りはこれを使う。

さて、今度は XAML を使わない場合、あるいは XAML の提供する PropertyChangedEventHandler を使わずに実装する場合を考えてみると、

  • Model: データプロパティのまとまり
  • View: 画面に表示する部分

となる場合、間に INotifyProperyChanged を入れる。Model のプロパティを変更した時に、INotifyProperyChanged を通じて、View に伝えるという訳です。
何故「伝えない」といけないかというと、

  • View は単独で、画面の再描画を行っている

という前提になっているためで、View が描画するときに Model の値を直接参照していれば、何も「伝える」ことをしなくても良いわけです。
これをコードで表すと、以下のような View::value, Model::value と分離されている場合(実装的にそうなっている場合)は、なんらかの形で、Model -> View への伝達が必要になります。

class View {
	int value;
	void Display() {...}
};
class Model {
	int value;
};

これが、INotify であったり、Listener パターンであったりします。このあたりの実装は、

誰かが使ったらアリスに知らせろ | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2017
誰かが使ったら誰かが使ったらアリスに知らせろ(2) | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2023

なところで、C++ の実装があります。(2)のほうは関数ポインタ(クラスのメソッドのポインタ)を使っているのですが、Model と View が密着し過ぎていてあまり良くありません。どうせならば、多対多の Model-View に合わせたいですよね。

話を元に戻すと、先の実装では、View::value を Model::value のポインタとすると、INotify が省けます。

class View {
	int *value;
	void Display() {...}
	void SetModel( Model *model ) {
		this->value = &(model->value);
	}
};
class Model {
	int value;
};

これで、View::value は常に Model::value と同じ値を持ちます。ポインタですからね。View から設定した vlaue の値も、常に Model::value に設定される(というかアドレスが同じ)なので、万々歳…てな感じなのですが、実は問題があります(C#/VBには、値を直接参照できる「参照/ポインタ」の概念がないので、これができません。もっとも、int を適当にオブジェクト化して参照させることも可能ですが、ちょっと冗長ですね)。

  • SetModel のところで、Model の型に縛られている。
  • View から入力された value に、Model::value が過敏すぎる

という問題があります。View と Model が密接過ぎるという点では、asp.net mvc では、dynamic という型を使って動的にプロパティを解決する手段を用意しています。mvvm の場合は、model の型依存になっているところもありますが、中間の vm で吸収する方法があります。あまりやりませんが、複数の model をひとつの viewmodel で扱えるようにして(model の本来の「データ」の位置に落とし込む)view との距離を保ちます。

なので、C# の INotifyPropertyCanged の実装までは調べていないのですが、View からは、リフレクションなどを使って「名前」を使って Model のプロパティ(アクセスメソッド)を呼び出していると考えられます。あるいは、com の場合は、invoke です。だから、Model::value -> View::value への伝達は、View からの pickup に「名前/文字列」を使っているのかなと。
ただし、「文字列」=「プロパティ名」(アクセッサ)というは、C#/VB の場合は文字列の生成が発生するので、ちょっと GC 的にどうなのかなぁ。と思うのですがどうなんでしょうねぇ? const で定義されていると一意なアドレスを取るのですが、引数に直接指定した「文字列」の扱いは不明ですね。。。と思って、調べてみると GetHashCode で同じ値が返ってきました。同一文字列を何度も生成されている訳ではなさそうです。という訳で、GC 的にも大丈夫。

もうひとつの問題点として、View の反応に過敏すぎる Model::value が出来てしまいます。このあたりは、view の実装依存なのですが、例えば Model::value の値の範囲を「0から100」と制限しているとき(validationしているとき)に、View では「0から100」以外の値が入らなってしまいます。この実装は、一見良いように見えますが、ユーザーのオペレーション(ユースケース)として、

  1. 最初に「99」が入っている。
  2. 「90」に変更しようと思う。
  3. 「990」と一時的に「0」を入力した後に「9」を消して、「90」と入れたいと思った。
  4. しかし、100以上になるので「0」が入力できない。

これは、一度「9」を消してから、というオペレーションになるのですが、人としてはちょっと面倒です。なので、view での検証と model での検証(特に永続化時の検証)を別にしたい時が多々あります。このためにも、

class View {
	int value;
	void Display() {...}
};
class Model {
	int value;
};

な風に、View::value と Model::value は分離しておきたい、しかし緩く結合しておきたいという意味合いがあります。

さて、c++ で INotify を実装する場合には、リフレクションや com の Invoke がないので、単純にプロパティの文字列では使えません。まぁ、c++/cx の場合は使えそうなのですが、これは別途。
INotify に文字列を渡す意味としては

  • model::value を一意に表す記号

の意味が強いので、c++ の場合は、

  • model のプロパティを、文字列から変換する map を作る
  • model のプロパティを、一意に認識する enum を作る
  • model のプロパティを、直接指定する関数ポインタを使う。

という方法があります。「文字列から変換する map を作る」のが、既存の PropertyChangedEventHandler を使う方法と近いのですが、いちいち map を作るのも面倒だし、model のプロパティが追加されると map も修正しないといけないという保守性の悪さがあります。このあたりは、#define マクロを使ってもいけそうな気がするのですが、ちょっと他を探りたいですね。
クラスメソッドのポインタを使おうと思ったのですが、クラスの型に過敏なために既に指摘したように model と view が密着しすぎます。できることならば、model と view とは全く独実して開発しても大丈夫な位に分離させたいものです。XAML の場合は、Model のプロパティ名を指定することが可能なので(実際のプロパティ名とプロパティの文字列は同一なのですが)、View の側から見れば ViewModel を媒介させることにより分離が可能です。ただし、この分離部分は、(現在の)間にある INotifyProperyChanged の実装に依り複数の Model への対応が難しくなっています。まぁ、手作業でちまちまということで。

目標とするところは、Multi doc-view を継承した形で、 multi model-view ってところなのですが、少しずつぼちぼちと考察してきますか。

カテゴリー: 開発, C++ パーマリンク

c++ で INotifyProperyChanged をどう実現するか? への2件のフィードバック

  1. masuda のコメント:

    ちょっと補足しておくと「分離」のところには、オブジェクト指向的な分離というものと、C++のヘッダファイル参照の分離のように実務的な分離の問題がある。どちらかというと、後者の分離のほうが重要で、無断なヘッダファイルの参照やDLLの参照を減らさないと、プログラムを作る時に何度もコンパイルする羽目になるという…こっちも含めて「分離」していかないと、開発スピードが滞るのですという話も含めて。

  2. ピンバック: c++ で INotifyProperyChanged をどう実現するか? | Moonmile Solutions Blog | S.F.Page

コメントは停止中です。