短いまとめ
「ビューにしか影響を与えないアクションもモデルを通過する」のが、すごい。
中ぐらいのまとめ
- Viewにしか影響を与えないアクションもModelを通過する
- Controllerの責務から「アクションをModelとViewに振り分ける」がなくなる
- アクションが(UIのイベントより)抽象的でないと実現できない
- アクションを設計するのは難しい
- アクションが設計できれば他のパターンでも上手くいく
- アクションの設計を強要したのが、すごい
背景
UIアーキテクチャーパターンを振り返ります。
MVC
The Model-View-Controller (MVC) Its Past and Presentに準拠します。
簡単な説明
- ドメインロジック
- M:Model データモデル更新ロジック + データモデル
- プレゼンテーションロジック
- V:View プレゼンテーションの更新
- C:Controller ユーザの入力処理
素晴らしい点
- 分割方針がわかりやすい。誰にでも扱える。
解決できない問題
- 役割が明確なモジュール(Model)が小さい
- 一般にビジネスロジックの量は少ない
- アプリケーション全体の1/3を期待する?実際は1/10程度
- ビューの状態をControllerが持つ
- Modelの役割はドメインロジック。ビューの状態はもちません
- ビューの状態
- 要素の選択状態
- ボタンの有効無効制御
- 要素追加後に自動選択すると、ビューの更新をモデルの更新完了後まで待つ必要がある。Supervising Controllerを導入できない
ModelとViewの役割は明確ですが、それ以外の役割は全てControllerが持ちます。
Controllerが巨大化します。
MVP
MVP: Model-View-Presenter
The Taligent Programming Model for C++ and Javaに準拠します。
簡単な説明
MVCのControllerを役割を細分化したパターン。
- ドメインロジック
- M:Model データモデル
- プレゼンテーションロジック
- S:Selection 選択状態
- C:Command データモデル更新ロジック + 選択状態更新ロジック
- P:Presenter ビューの状態 + ビューの状態更新ロジック + Commandの呼び出し
- I:Interactor ユーザの入力処理
- V:View プレゼンテーションの更新
選択状態やビューの状態をもつ専用のオブジェクトを導入します。
素晴らしい点
- Selectionを導入し、ビューがModelとSelectionを両方監視することで、Supervising Controllerが導入できる
- CommandをControllerから分離できる
解決できない問題
- 複数のビューを更新したい時はPresenterが頑張る
- 編集モードとreadonlyモードの切り替えはPresenterが頑張る
- モデル更新イベントとビュー更新イベントがどちらもPreseterを通る。Presenterはモデル更新とビュー更新の振り分けを行う
Presenterが巨大化します。
プレゼンテーションモデル
Presentation Modelに準拠します。
簡単な説明
MVCのControllerを画面の要素で分割したパターン。
例えば、リストとリストの表示要素、それぞれにPresentationModelを作ります。
- ドメインロジック
- M:Model データモデル更新ロジック + データモデル
- プレゼンテーションモデル
- PM:PresentationModel ビューの状態 + Mを表示しやすいように変換。
- V:View プレゼンテーションの更新
素晴らしい点
- 巨大化するControllerを分割できる
- PresentationModelは画面構成と対応するツリー構造になる。構成がわかりやすい
解決できない問題
- PM間のやりとりは親PMが管理
- PMとModelが1対1で対応していない場合、対応付けの解決は親PMが行う
- モデル更新イベントとビュー更新イベントがどちらもPMを通る。PMはモデル更新とビュー更新の振り分けを行う
上位のPresentationModelが巨大化します。
残っている課題
高度なアプリケーションを作ると
ユーザーの操作をModelとViewまたは他のPresentationModelに振り分ける処理が
プレゼンテーションロジックに集中します。
既存のMVC、MVP、プレゼンテーションモデルは、これを解決しません。
本題
unidirection data flow を見ます。
MVCな視点で見ると、以下になります。
- ドメインロジック
- Store
- プレゼンテーションロジック
- Action Creators : ユーザーの入力処理
- React Views : プレゼンテーションの更新
構成要素は(Dispatcherを除くと)MVCとあまり変わりません。
MVCとの違い
「ビューにしか影響を与えないアクションもモデルを通過する」
ビューにしか影響を与えないアクション例:
- 選択
- 複数要素を選択して編集する際の、選択状態
- 非表示切り替え
- TODOリストの完了項目を非表示にする
- リストの明細を表示する
- Read Onlyモード切り替え
これらのアクションが一度はStoreを通る(けど何もしない)のがすごい。
「ビューにしか影響を与えないアクションもモデルを通過する」のすごさ
MVCが生まれてから四半世紀、いろいろ分割してみたけど
ユーザーの操作をModelとView(他のPresentationModel)に振り分ける処理が
プレゼンテーションロジックに集中する。
を回避する方法は発見されませんでした。
「ビューにしか影響を与えないアクションもモデルを通過する」とするだけで
モデル(Store)もビューも「自分の興味があるアクションにだけ、反応すれば良く」なりました
問題点
- アクションがflowの途中で破壊された場合(バグ)、発生箇所を特定するのが面倒
- なんでもflowを一周するのが面倒
- 「入力欄に文字が入っているときだけ、ボタンを押せる」インタラクションを作るのが面倒
- アクションの設計が難しい
- ユーザー操作を上手く抽象化できれば、プレゼンテーションモデルだって、PM間の通信も上手く整理できる
まとめ
「ユーザー操作を抽象化しない」逃げ道を塞ぎました
-
機能が増えても破綻しない設計
-
設計・実装コストが高い
unidirection data flowアーキテクチャーの最大の特徴はここだとと思います。
(少し知見がたまると、違う特徴が見えてくる可能性はあります。)
fluxの補足
Dispatcherが必要な理由
- アクションの投げ先がシングルトンだと、ActionCreatorをViewのパーツごとに分けたときに投げ先を取得しやすい
- 一つのActionが複数のデータモデル・Viewを変更する時、Actionを分割する
- 例えばカスケード削除。
- 分割したActionの順序を制御する
- 例えばカスケード削除。データモデルは大抵どの順番で消しても大丈夫。Viewは削除順に制約があることがある
Constantsが必要な理由
- アクションの名前をAction Creator、Dispatcher、Storeで参照する。定数定義があると補完しやすい。