17

これは完全にベストプラクティスタイプの質問であるため、言語は関係ありません。私はMVCの基本原則を理解しており、MVCにはさまざまな微妙なフレーバーがあることを理解しています(つまり、モデルを直接参照するビューと、コントローラーからのデータデリゲート)。

私の質問は、MVCがネストされている場合のクロスMVC通信に関するものです。この例としては、描画プログラム(ペイントなど)があります。Canvas自体はMVCである可能性がありますが、描画された各エンティティ(Shapes、Textなど)も同様です。CanvasModelモデルの観点からは、エンティティのコレクションを持つことは理にかなっていますが、エンティティビューとコントローラーの対応するコレクションをそれぞれ持つ必要がCanvasViewありますか?CanvasController

また、新しく描画されたエンティティを追加するための最良/最もクリーンな方法は何ですか?ユーザーがCircleToolをアクティブにしている場合、キャンバスビューをクリックして、図形の描画を開始します。は、リッスンできるCanvasView関連するマウスのダウン/移動/アップイベントを発生させる可能性があります。CanvasControllerその後、コントローラーは基本的にこれらのイベントをCircleTool(状態パターン)にプロキシできます。マウスを下に向けると、CircleToolは新しいCircleを作成します。ツールは新しいCircleEntityController完全なものを作成し、次のようなものを呼び出す必要がありますcanvasController.addEntity(circleController)か?サークルのモデルとビューを作成する責任はどこにあるべきですか?

これらの質問がやや曖昧な場合は申し訳ありません:)

-編集-

これが私が話していることの疑似コディッシュの例です:

CircleTool {
    ...
    onCanvasMouseDown: function(x, y) {
        // should this tool/service create the new entity's model, view, and controller?
        var model = new CircleModel(x, y);
        var view = new CircleView(model);
        var controller = new CircleController(model, view);

        // should the canvasController's add method take in all 3 components
        // and then add them to their respective endpoints?
        this.canvasController.addEntity(model, view, controller);
    }
    ...
}


CanvasController {
    ...
    addEntity: function(model, view, controller) {
        // this doesn't really feel right...
        this.entityControllers.add(controller);
        this.model.addEntityModel(model);
        this.view.addEntityView(view);
    }
    ...
}
4

7 に答える 7

18

うわー、まあ、私はおそらくこの質問に対する驚くべき答えを持っています:私はMVCがプログラミングにおけるこの素晴らしい完璧のシンボルとどのように見なされているかについて長年の怒りを持っています。面接でのお気に入りの質問は、「MVCで遭遇する可能性のあるいくつかの問題または課題は何ですか?」です。質問が困惑した、おかしな表情で迎えられる頻度は驚くべきものです。

答えは非常に単純です。MVCは、単一の共有モデルオブジェクトからニーズを満たす複数のコンシューマーの概念に依存しています。さまざまな見方が異なる要求をするとき、物事は本当に地獄に行き始めます。数年前に、著者が階層MVCの概念を発展させた記事がありました。他の誰かがコメントに来て、彼らが発明していると思っていたものがすでに存在していると彼らに話しました:Presentation-Abstraction-Controller(PAC)と呼ばれるパターン。おそらく、パターンの文献でこれを目にするのは、ブッシュマンの本で、ギャングオブファイブまたはPOSA(パターン指向ソフトウェアアーキテクチャ)と呼ばれることもある唯一の場所です。興味深いことに、PACを説明するときはいつでも、完璧な例としてペイントプログラムを使用しています。

主な違いは、PACでは、プレゼンテーション要素が独自のモデルを持つ傾向があることです。これはPACのAです。各コンポーネントについて、必要はありませんが、抽象化することができます。ここでの他のいくつかの応答によると、何が起こるかというと、調整コントローラーがあります。この場合、それがキャンバスを支配します。キャンバスの側面に、さまざまな形状(たとえば、正方形3、円5など)の数を示す小さなビューを追加するとします。そのコントローラーは調整コントローラーに登録して、elementAddedとelementRemovedの2つのイベントをリッスンします。通知を受け取るたびに、独自の抽象化で持っていたマップを更新するだけです。多数のコンポーネントがそのようなもののサポートを追加するために使用している共有モデルを変更することがどれほどばかげているか想像してみてください。さらに、

階層的な部分は、PACにコントローラーのレイヤーが存在する可能性があることです。たとえば、Canvasレベルでコントローラーを調整すると、特殊な動作をするさまざまなコンポーネントについては認識されません。ドラッグなどを受け入れるためのロジックが必要な他のものを含むことができるシェイプを作成する場合があります。そのコンポーネントには独自の抽象化とコントローラーがあり、コントローラーはそのアクティビティをCanvasControllerなどと調整します。含まれているコンポーネントと通信するためのいくつかのポイント。

これがPOSAブックです。

于 2013-03-13T04:14:11.883 に答える
1

これを攻撃する方法はたくさんあります。私はここに投稿された他の答えに同意しません。

ただし、これを行う1つの方法は、オブザーバーパターンを使用することです。そしてその理由を説明させてください。

これらの描画ツールはキャンバスなしでは何もないので、ここではオブザーバーパターンが必要です。したがって、キャンバスがない場合は、サークルツールを呼び出すことはできません(または呼び出さないでください)。代わりに、私のキャンバスには多数のオブザーバーがあります。

キャンバスで使用できる各ツールは、監視可能なイベントとして追加されます。次に、「描画の開始」などのイベントが発生すると、ツールがコンテキスト(この場合は「円」)として送信されます。そこから、サークルツールのアクションが実行されます。

これを想像する別の方法は、各レイヤーに独自のサービス、モデル、およびビューがあることです。コントローラは実際には外部レベルにあり、キャンバスに関連付けられています。したがって、サービスは他のサービスまたはコントローラーによってのみ呼び出されます。サークルツールコントローラーはありません。そのため、別のサービス(この場合は監視対象のイベント)のみが呼び出すことができます。そのサービスは、データの集約、モデルの構築、およびビューの提供を担当します。

于 2013-03-09T01:51:34.050 に答える
0

私はMSPaintのような動作を想定しています。この動作では、アクティブなツールが、ユーザーが満足するまで操作できるベクトルグラフィックグリフを作成します。ユーザーが満足すると、グリフはピクセルのラスターである画像に書き込まれます。


サークルツールが選択されたら、CanvasControllerに以前アクティブだったツールのMVCトリオ(別のツールがアクティブだった場合)の選択を解除して、新しいCircleToolController、CircleModel、CircleViewを作成します。以前にアクティブだったグリフがfinalになり、CanvasModelに描画されます。

CanvasViewには、CircleViewへの参照を渡す必要があります。これにより、CircleViewが描画される前に、CanvasModelのピクセルを画面に描画できるようになります。画面への円の実際の描画、CircleViewに委任します。

したがって、CircleViewは、CircleModel以外のより一般的なモデルクラスを認識して観察する必要があります。色の選択/パレットモデル、塗りつぶしスタイルや線の太さなどのモデルを考えています。これらの他のモデルは長持ちします。アプリケーションと同じように、独自のビューとコントローラーがあります。結局のところ、これらはアプリケーションの他の部分とはまったく別のものです。


補足として:CanvasViewによるCanvasModel(ピクセルカラーのラスター)の描画を、画面全体の更新の調整から実際に分割することができます。CanvasViewとアクティブなGlyphView(CircleViewなど)を認識している上位レベルのPaintViewを使用して、CanvasViewとGlyphViewの間の描画を調整します。

于 2013-03-12T23:39:24.253 に答える
0

私の観点からすると、ネストされたMVCコンポーネントを使用することは、ここではやややり過ぎです。各時点で、モデルには複数の要素が含まれます(別の回答で説明されているように、複合パターンを使用してネストされた構成要素である可能性があるさまざまな円、正方形など)。 。 ただし、要素を表示するキャンバスは1つのビューにすぎません。(そして、単一のビューに対応して、必要なコントローラーは1つだけです。)

複数のビューを持つ1つのケースは、要素のリスト(たとえば、キャンバスの横に表示されます)である可能性があります。次に、キャンバスと要素リストを1つの同じモデルに2つの異なるビューとして実装できます。

要素の追加を「最適に」実装する方法の質問に関して:次の一連のイベントを検討します。

  • ビューは、新しい円要素が描画されたことをリスナーに通知します(たとえば、中間点と初期半径をパラメーターとして使用します)。
  • コントローラはビ​​ューのリスナーとして登録されているため、コントローラの「draw-circle(point、radius)」リスナーメソッドが呼び出されます。
  • コントローラは、モデルに新しいサークルインスタンスを作成します(直接、またはモデルの一部であるファクトリクラスを介して-新しい要素の作成を実装する方法はたくさんあると思います)。コントローラは(文字通り)「制御」されているため、新しい要素をインスタンス化する(または少なくともインスタンス化をトリガーする)のはコントローラの責任であると私は信じています
  • モデルでは、ある種の「要素の追加」メソッドが前のステップで呼び出されます。
  • モデルは、すべてのリスナーに「新しい要素が作成されました」という通知を発行します(おそらく、新しく作成された要素への参照を渡します)。
  • キャンバスはモデルのリスナーとして登録されているため、キャンバスの「新しい要素が作成されました(要素)」リスナーメソッドが呼び出されます。
  • 後者の通知への応答として、キャンバスは(それ自体に)円を描きます。
于 2013-03-12T18:09:54.373 に答える
0

RenderingService より適切な名前がないため-形状の相互作用を管理するもの)は、新しいCircle ドメインオブジェクトをインスタンス化し、 (直接またはビューが新しいデータを要求しているときに)ビューに通知します。

すべてのアプリケーションロジック(ストレージ抽象化とドメインオブジェクト間の相互作用)をプレゼンテーション層(この場合はコントローラー)にダンプする習慣がまだ残っているようです。

PSあなたはHMVCについて話しているのではないと思います。

于 2013-02-22T14:38:29.370 に答える
0

これは単なるアイデアですが、メディエーターパターンが適用可能かどうかを検討してください。

4人組から:

意図

オブジェクトのセットがどのように相互作用するかをカプセル化するオブジェクトを定義します。Mediatorは、オブジェクトが相互に明示的に参照しないようにすることで緩い結合を促進し、オブジェクトの相互作用を個別に変更できるようにします。

適用性

メディエーターパターンを使用する場合

  1. オブジェクトのセットは、明確に定義されているが複雑な方法で通信します。結果として生じる相互依存性は構造化されておらず、理解するのが困難です。
  2. オブジェクトは他の多くのオブジェクトを参照して通信するため、オブジェクトの再利用は困難です。
  3. 複数のクラスに分散されている動作は、多くのサブクラス化なしでカスタマイズ可能である必要があります。

結果

Mediatorパターンには、次の長所と短所があります。

  • サブクラス化を制限します。メディエーターは、そうでなければ複数のオブジェクトに分散される動作をローカライズします。この動作を変更するには、メディエーターのみをサブクラス化する必要があります。同僚のクラスはそのまま再利用できます。
  • それは同僚を切り離します。メディエーターは、同僚間の緩い結合を促進します。同僚クラスとメディエータークラスを個別に変更して再利用できます。
  • オブジェクトプロトコルを簡素化します。メディエーターは、多対多の相互作用を、メディエーターとその同僚の間の1対多の相互作用に置き換えます。1対多の関係は、理解、維持、および拡張が容易です。
  • オブジェクトがどのように連携するかを抽象化します。メディエーションを独立した概念にし、それをオブジェクトにカプセル化することで、オブジェクトが個々の動作とは別にどのように相互作用するかに焦点を当てることができます。これは、オブジェクトがシステム内でどのように相互作用するかを明確にするのに役立ちます。
  • 制御を一元化します。メディエーターパターンは、相互作用の複雑さをメディエーターの複雑さと交換します。メディエーターはプロトコルをカプセル化するため、個々の同僚よりも複雑になる可能性があります。これにより、メディエーター自体を維持するのが難しいモノリスにすることができます。
于 2013-03-07T19:55:38.067 に答える
0

もし私があなたなら、シェイプを扱うときにコンポジットパターンを選ぶでしょう。円、正方形、長方形、三角形、文字などがあるかどうかに関係なく、私はすべてを形として扱います。一部の形状は線のような単純な形状にすることができ、他の形状はグラフや円グラフなどのより複雑な複合形状にすることができます。基本形状と高度な(複雑な)形状を参照する基本クラスを定義することをお勧めします。基本形状と高度な形状はどちらも同じタイプのオブジェクトであり、その高度な形状には、この複雑なオブジェクトの定義に役立つ子を含めることができます。

このロジックに従うことで、各シェイプがそれ自体を描画し、各シェイプがその位置を認識するロジックを使用できます。それに基づいて、特定のロジックとアルゴリズムを適用して、クリックされたシェイプと各シェイプがイベントに応答できるかどうかを確認します。

GoFの本によると:

意図

オブジェクトをツリー構造に構成して、部分全体の階層を表します。Compositeを使用すると、クライアントは個々のオブジェクトとオブジェクトの構成を均一に扱うことができます。

動機

図面エディタや回路図キャプチャシステムなどのグラフィックアプリケーションを使用すると、ユーザーは単純なコンポーネントから複雑な図を作成できます。ユーザーは、コンポーネントをグループ化して、より大きなコンポーネントを形成できます。[...]

複合パターンの鍵は、プリミティブとそのコンテナの両方を表す抽象クラスです。グラフィックシステムの場合、このクラスはGraphicsです。グラフィックは、グラフィックオブジェクトに固有の描画などの操作を宣言します。また、子にアクセスして管理するための操作など、すべての複合オブジェクトが共有する操作も宣言します。

さて、あなたの問題に戻りましょう。前述のように、基本関数の1つはDrawメソッドです。このメソッドは、次のシグネチャを使用して基本クラスの抽象メソッドとして実装できます。

public virtual void Draw(YourDrawingCanvas canvas);

各図形は、独自のバージョンのDrawを実装します。それらを渡すという引数は、各形状がそれ自体を描画する描画キャンバスへの参照です。各図形は、その内部構造にそれ自体の参照を格納でき、マウスのクリック位置をこれらの座標と比較して、どの図形がクリックされたかを通知するために使用できます。

于 2013-03-12T08:36:35.803 に答える