質問: 主にUnityでの開発を最近学び始めたのですがMVP設計の依存性についてわからない箇所があるのでご教授お願いしたいと思います。 MVP設計では通常以下の形式になると思います。 ``` Model ← Presenter → View ⇨ ⇦ →が依存 ⇨が変更を通知 ``` この時PresenterがViewとModelを扱う上位レイヤーであるため、依存性逆転の原則からして上位レイヤーのPresenterがViewとModelに依存するのはあまり好ましくないのでしょうか。 またもう少し具体的に考えた時、Viewが増えた際に毎回Controllにも大幅に手を加えなければいけないのは若干好ましくないように感じます。 よって上位レイヤーは抽象に依存させるよう考えると以下の形式になるのではなると思いました。 ``` Model ➡️ IModel ↑⇩ Presenter ↓⇧ IView ⬅️ View →が依存 ⇨が変更を通知 ➡️が継承 (分かりづらくてすみません) ``` この形であればModelとViewの方がPresenterに依存するようになると思うのですがどうにも自信が持てずにいます。 何か私の理解で間違っている箇所、及びこの形の問題点等あればご教授よろしくお願いします。
回答: > > Viewが増えた際に毎回Controllにも大幅に手を加えなければいけないのは若干好ましくない > > > これを抽象化で解決するのは正しい方法の一つです。 しかしそれは依存性逆転ではなく、汎化による共通化をしているのです。 MVCについて MVCやMVPで最も重要なのは、"Modelが他に依存しないこと"です。 その理由は、"変更の多いモジュールが変更の少ないモジュールに依存すべき"だからです。 そしてもう一つ重要な概念として、ModelとViewを分けるフレームワークでは、 "Viewがモデルのデータを取ってくるタイプ"と"ModelのデータをViewに配信するタイプ"、があります。(複合タイプもあります) 上の2つを合わせて考えると以下のようになります。 Viewがモデルのデータを取ってくるタイプ: `Model ← View` 変更の多い方が変更の少ない方に依存しているので問題ない ModelのデータをViewに配信するタイプ: `Model → View` 変更の少ない方が変更の多い方に依存してしまっているのでまずい。 そこで間にControllerを入れることでViewの変更がModelに影響しないようにする。 `Model ← Controller → View` これも立派な依存性逆転です。 依存性逆転について 依存性逆転を行う理由は2つあります。 一つは、"変更の少ない箇所が変更の多い箇所に依存しないようにするため"です。 これは、MVCのようにクラス間の依存性で解決できます。 もう一つは、"部品を交換可能にして再利用するため"です。 これには多くの場合インターフェースが用いられます。 部品交換の目的を持たない場合、インターフェースはただの"公開メンバ一覧"になり、 公開メンバを追加編集するたびに、インターフェースとクラスの両方を記述するという意味のない作業を行うことになります。 MVCのそれぞれについて、抽象化すべきかどうか考えてみましょう。 **Model** "変更の少ない箇所が変更の多い箇所に依存しないようにするため"の視点から見ると、Modelを抽象化する必要はありません。 なぜならModelとControllerの関係は始めから、変更の多い方から変更の少ない方への依存になっているからです。 また、インターフェースは呼び出し側が持つものなので、呼び出し側(Controller側)のIModelの定義に合わせてModelを実装すると、依存性が悪い方向に逆転してしまいます。 "部品を交換可能にして再利用するため"の視点からも、Modelを抽象化する必要はありません。 なぜならModelとはアプリケーションの核となるビジネスロジックなので、これを交換することはありえないからです。 ガワ替えは接続先のデータベースの変更で、 接続方法の変更はModelではなくコンポーネントの交換で、 Viewを他のアプリで使いまわすのはViewの機能分割です。 どれも"Modelの交換"ではありません。 **View** "変更の少ない箇所が変更の多い箇所に依存しないようにするため"の視点から見ると、Viewを抽象化する意義は微妙です。 なぜならViewはもともと変更の多いモジュールだからです。 "部品を交換可能にして再利用するため"の視点から見ると、Viewを抽象化する価値はありそうです。 異なるプラットフォームに対応する際にまるごと交換することができますし、 2Dのゲームを後々3Dに移植することなどが容易になります。 ただ、開発段階から手入力で抽象化を行うことには少し待ってください。 交換する対象もないのに定義を2重で書くことに意味はありませんし、 インターフェースの抽出は完成後にツールを使って一括で行うことができます。 **Controller** "変更の少ない箇所が変更の多い箇所に依存しないようにするため"の視点から見ると、Controllerを抽象化する意義も微妙です。 理由はViewと同じく、変更の多いモジュールだからです。 "部品を交換可能にして再利用するため"の視点から見ても、Controllerを抽象化する意義はほとんどなさそうです。 なぜならControllerは誰からも参照されていないので、交換することができないからです。 Viewがコントローラーを参照する場合や、Viewとコントローラーが相互依存するフレームワークなども見かけますが、 Modelに強く依存したControllerを交換することはModelを交換することと同じなので、 やはりControllerを交換することはできなさそうです。 まとめ ・Modelが他に依存しない限り、残りの依存性は開発するアプリケーションによって柔軟に変えていけばよい ・交換しない部分を抽象化しても、定義を2重に行うことになり意味がない ・交換のための抽象化は、開発完了後にツールで行えばよい 以上です!