9

私はビジターパターンとは何か、そしてそれをどのように使うかを知っています。この質問はこれと重複していませ


自分が書いた再利用可能なコードのほとんどを配置し、ほとんどのプロジェクトにリンクするライブラリがあります。

多くの場合、一部のクラスに機能を追加する必要がありますが、これらの新しい機能をライブラリに追加する必要はありません。実際の例を使用しましょう:

このライブラリには、、、およびによって継承されたクラスがありShapeます。CircleShapePolygonShapeCompositeShape

私は現在、これらをレンダリングする必要があるグラフィカルアプリケーションを開発していますが、使用するプロジェクトの一部はレンダリングを行わず、他のグラフィカルプロジェクトは使用する可能性があるため、コアクラスにShape仮想関数を配置したくありませんさまざまなレンダリングエンジン(このプロジェクトではQtを使用していますが、ゲームではOpenGLを使用するため、関数にはさまざまな実装が必要になります)。renderShapeShaperender

もちろん、これを行う最も有名な方法は、Visitor Patternを使用することですが、これは私の心にいくつかの疑問を投げかけます。

私のように、どのライブラリのどのクラスも拡張する必要がありますShape。ほとんどの公共図書館(それらのすべてについて)は、VisitorPatternをサポートしていません。なぜ?どして私がこんな事に?

ビジターパターンは、C++でダブルディスパッチをシミュレートする方法です。applyVisitorこれはC++でネイティブではなく、明示的に実装する必要があるため、クラスインターフェイスがより複雑になります。関数をクラスの関数と同じレベルにする必要はないと思います。これは抽象化を破るようなものです。

明示的にアップキャストShapeするdynamic_cast方が費用がかかりますが、私にはそれはよりクリーンなソリューションのように見えます。


だから、私は何をすべきですか?すべてのライブラリクラスにダブルディスパッチを実装しますか?提供しているライブラリが私のものShapeではなく、インターネット上にあるGPLライブラリが見つかった場合はどうなりますか?

4

5 に答える 5

15

最初:「ビジターパターンは、C++でのダブルディスパッチをシミュレートする方法です。」これは、えーと、完全には正しくありません。実際、ダブルディスパッチはマルチディスパッチの1つの形式であり、C ++で(欠落している)マルチメソッドをシミュレートする方法です。


クラス階層での操作を仮想関数の追加または訪問者の追加のどちらで実装するかは、クラスの追加と操作の追加の確率によって決まります。

  • クラスの数が操作の数よりも急速に変化し続ける場合は、仮想関数を使用してください。これは、クラスを追加するには、すべての訪問者を変更する必要があるためです。
  • オペレーション数に比べてクラス数が比較的安定している場合は、ビジターを使用してください。これは、仮想関数を追加するには、階層内のすべてのクラスを変更する必要があるためです。

はい、多くのライブラリにはビジターインターフェイスが付属していません。
上記の理由を見ると、クラスの数が頻繁に変わる場合はこれが正しいでしょう。つまり、ライブラリが頻繁にリリースされ、新しいクラスが絶えず追加されている場合、訪問者インターフェイスを提供することはあまり意味がありません。新しいリリースが新しいクラスをもたらすたびに、ライブラリを使用するすべての人がすべての訪問者を適応させる必要があるためです。 。したがって、上記の理由だけを見ると、ビジターインターフェイスは、libのクラス階層内のクラスの数がほとんどまたはまったく変更されない場合にのみ役立つように思われます。

ただし、サードパーティのライブラリには別の側面があります。通常、ユーザーはライブラリ内のクラスを変更できません。つまり、操作を追加する必要がある場合、これを実行できる唯一の方法は、訪問者を追加することです。ライブラリが、ライブラリに接続するためのフックを提供している場合です。
したがって、ライブラリを作成していて、ユーザーがライブラリに操作を追加できるようにする必要があると感じた場合は、訪問者をライブラリに接続する方法を提供する必要があります

于 2010-11-14T13:25:22.900 に答える
0

考えられる解決策はたくさんありますが、これを行うことができます。たとえば、次のようになります。Shapes特定のContext:でレンダリングされる新しい階層を開始します。

// contracts:

class RenderingContext {
public: virtual void DrawLine(const Point&, const Point&) = 0; 
    // and so on...
};

class ShapeRenderer {
public: virtual void Render(RenderingContext&) = 0;
};

// implementations:

class RectangleRenderer : public ShapeRenderer {
 Rectangle& mR;

public: 
 virtual void Render(RenderingContext& pContext) {
   pContext.DrawLine(mR.GetLeftLower(), mR.GetRightLower());
   // and so on...
 }

 RectangleRenderer(Rectangle& pR) : mR(pR) {}
};
于 2010-11-14T12:55:40.783 に答える
0

私はあなたが言ったことを完全に理解しています、そして私は同じ懸念を共有します。問題は、ビジターパターンがあまり明確に定義されておらず、その元の解決策が誤解を招くことです、IMHO。これが、このパターンのバリエーションが非常に多い理由です。

特に、正しい実装はレガシーコードをサポートする必要があると思います。つまり、ソースコードをまったく失ったバイナリですね。これは定義が言っていることです:元のデータ構造を変更する必要は決してないはずです。

私はvisitA、visitB、visitWhatever、acceptA、acceptB、acceptWhateverの実装は好きではありません。これは絶対に間違っています、私見。

機会があれば、私が書いた記事をご覧ください。

これはJavaですが、目的に役立つ場合はC++に簡単に移植できます。

お役に立てば幸いです

乾杯

于 2011-01-29T23:30:34.340 に答える
0

これは、私にはビジターパターンの場合のようには見えません。

RenderableShapeオブジェクトを集約するクラスをShape作成してから、形状ごとにサブクラスを作成することをお勧めします。RenderableShape仮想renderメソッドがあります。

複数のレンダリングエンジンをサポートする場合は、RenderContext描画操作を抽象化する基本クラスを作成できます。各レンダリングエンジンにはサブクラスがあり、各サブクラスはレンダリングエンジンの観点から描画操作を実装します。次に、引数としてRenderableShape::renderaを取り、抽象化されたAPIを使用してそれに描画します。RenderContext

于 2010-11-14T12:40:12.683 に答える
0

したがって、クラスxxxShapeがあり、レンダリングを「駆動」する情報が何らかの形で含まれています。中心、半径の可能性がある円の場合、正方形の場合、いくつかのコーナー座標など。たぶん、詰め物や色についての他のいくつかのもの。

実際のレンダリングロジックを追加するためにこれらのクラスを更新したくない/更新できないので、そうしない理由は有効/不可避だと思います。

しかし、おそらく、クラスには「運転」情報を取得するのに十分なパブリックアクセスメソッドがあります。そうしないと、運命にあります。

それで、その場合、なぜあなたはこれらのアイテムを単に包むことができないのですか?

 CircleRenderer hasA Cicle, knows how to render Circles

等々。次に、レンダラークラス全体でVisitorパターンを使用します。

于 2010-11-14T12:41:41.157 に答える