導入
パターンを学ぶのと同じくらい、やってはいけない形を知っておくとレビューで危険に気づけます。ここまで作ってきたTaskListViewModelを思い出しながら、MVVMで特によく起きる失敗を見ていきましょう。
説明
flowchart TB
A["Fat ViewModel<br/>検証・変換・データアクセスまで全部抱える"] --> R1["→ Serviceへ委譲(第5-6章のTaskService分離)"]
B["Controllerが詳細を知りすぎる<br/>ViewModelのフィールドへ直接アクセス"] --> R2["→ getter/setterとCommandだけでやり取り"]
C["ViewModelがJavaFXの型を知る<br/>AlertやStageを直接参照"] --> R3["→ ダイアログ表示を抽象化して挟む"]
D["initialize()に業務ロジック<br/>ボタンのハンドラに直接処理を書く"] --> R4["→ Commandへ移す"]
style A fill:#ffebee
style B fill:#ffebee
style C fill:#ffebee
style D fill:#ffebee
- Fat ViewModel(神クラスのMVVM版): 一覧管理も、検証も、データアクセスも――全部
TaskListViewModelに書いてしまうと肥大化する。このコースのTaskListViewModelが薄く保てているのは、業務ルールをTaskService(第5章)に委譲しているからにほかならない。ViewModelの責務は「画面の状態を持つ」「操作をCommandとして公開する」の2つに絞る - Controllerが ViewModelの詳細を知りすぎる:
viewModelのフィールドに直接アクセスしたり、instanceofで具体クラスを判定してしまうと、ViewModelを差し替えられなくなり、テストする意味も薄れる。ControllerはViewModelの公開されたgetter/setterとCommandを通じてしか触れないようにする - ViewModelがJavaFXの型を知る:
TaskListViewModelの中でAlertやStageを直接生成してしまうと、ViewModelはブラウザでは動かせないコード(このコース自体が成立しないコード)になり、単体テストもできなくなる。「確認ダイアログを出す」という操作を抽象化したインターフェースに切り出し、実装はView側(またはJavaFXに依存する層)に置く initialize()やsetOnActionに業務ロジックを書く: ボタンのイベントハンドラの中に直接タスク追加処理を書いてしまうと、MVVMで分離した意味が消え、そのロジックはテストできない。必ずCommand経由でViewModelのメソッドを呼ぶ(第7章 レッスン26のaddButton.setOnActionが、viewModel.getAddCommand().execute()を呼ぶだけになっていたのはこのため)
パターンと同じく、アンチパターンも「知っていれば避けられる」ものです。神クラス・マジックナンバーのような一般的なアンチパターンと同じ理屈が、MVVMという文脈にもそのまま当てはまります。
まとめ
- Fat ViewModelは業務ルールをServiceへ委譲して防ぐ
- Controllerは公開されたgetter/setter・Commandだけを通じてViewModelとやり取りする
- ViewModelはJavaFXの型を直接知らない(ダイアログ等は抽象化する)
- 業務ロジックはイベントハンドラでなくCommand経由で呼ぶ
次章: TDDの総仕上げとして、仕様変更・リファクタリング・統合テストに挑みます。