3

階層順に積み重ねることができる長方形があるシステムに取り組んでいます。したがって、ベースは次のようなものです。

Rect parent;
Rect child;
parent.addChild(&child);
...
Rect* someChild = parent.getChildAt(1);

ここまでは簡単に実装できます。しかし、これらの長方形はさまざまな機能を実装できるはずです。これは、シリアライザー、スタイラー、ドロワーなどである可能性があります...

これで、多重継承が一種の「ダメ」であることがわかりましたが、この場合、次のような構文が望ましいと思います。

class StackableStyleableRect: publicRect, public Stackable, Styleable{}
class StackableStyleableDrawableRect: public Rect, public Stackable, Styleable, Drawable{}

私は、正しく理解すれば上記を可能にする奇妙な繰り返しのテンプレートパターン (crtp) に出くわしました。このようなもの:

class Rect{
    public:
       float width;
       float height;
}

template <class RectType>
class Stackable{
    public:
        void addChild(RectType* c){
             children.push_back(c);
        }
        std::vector<RectType*> children;
}

template <class RectType>
class Drawable{
    public:
        virtual void draw(){
            RectType* r static_cast<RectType>(this);
            drawRect(r->width, r->height);
        }
}

template <class RectType>
class Styleable{
    public:
        int r, g, b;
}

class CustomRect: public Rect, public Stackable<CustomRect>, public Drawable<CustomRect>{
}

class CustomRectWithStyle: public Rect, public Stackable<CustomRect>, public Drawable<CustomRect>, public Styleable<CustomRect>{
    public:
}

この理由は、さまざまな種類の Rect 型でコードを再利用したいからです。あるプロジェクトでは、スタイルを設定する必要はありませんが、別のケースでは、提供されているすべての機能が必要になります。このように必要な機能を選択することで、物事をすっきりさせ、機能を分離することもできます。

これでいくつかの基本的なテストを行い、期待どおりに動作しますが、時間の経過とともに構文が過度に複雑になる可能性があると感じています.

また、ある時点で、コンポーネントを相互に依存させたり、コンポーネントが存在する場合に異なる動作をさせたりすると便利です。(たとえば、Drawable の draw 関数は、Styleable が存在する場合は自動的にその色を使用できます)

今、遅かれ早かれトラブルに遭遇する運命にあるのでしょうか、それともうまくいくでしょうか? よりフィットする別のパターンはありますか?それとも、「適切な」C ++でこのようなことを行うことは単に不可能ですか?

4

3 に答える 3

3

それぞれに長所と短所があるさまざまな解決策を提案する前に、この問題に対するあなたのアプローチの欠点をいくつか指摘させてください。

多重継承アプローチの欠点

  • クラス/インターフェースから継承するStackableと、集約データ構造の知識が、データを表すクラスに組み込まれます ( Rect)。Rectこれは、 s が複数のデータ構造 (たとえば、ルックアップ用のツリー) に入る必要がある場合や、データ構造にも s が必要な場合に、制限になる可能性がありCircleます。このソリューションにより、後でデータ構造を交換することも困難になります。
  • さまざまなクラスの組み合わせが多数あると、時間の経過とともに維持しなければならないクラスの量が管理不能になる可能性があります。
  • 他の誰かがあなたのコードで作業する必要がありますが、 type を定義したことに気付かない場合StackableStyleableDrawableRectDrawableStackableStyleableRect次に、同じ機能を提供しますが、クラスと同じではない独自の を定義します。最良のシナリオでは、プロジェクトに冗長なコードが含まれています。さらに悪いことに、どちらかを使用するコードベースの部分が既に存在するため、両方のクラスを混在させて使用する必要がある場合、問題と混乱が発生します。
  • s のサイズ変更など、別の問題がプログラムに導入されるRectと、既存のクラスをすべて変更するか、それとも新しいクラスを作成するか? に変更して、既存のコードベースへの変更StackableStyleableDrawableRectStackableStyleableDrawableResizeableRect促しますか、それともまったく新しいクラスとして作成しますか?
  • もちろん、多重継承では、注意を怠ったり、ある日 aが aと a の両方であると判断したりして、 のようなものを呼び出す必要がある場合、ひし形の問題が発生する危険があります。RectGDIDrawableDirectXDrawableDrawable::logDrawingOperation()

したがって、a が Drawable や Stackable などであることは些細なことのように思えるかもしれませんがRect、このアプローチは扱いにくく、多くの欠点があります。この場合、Rect単純な長方形以外のビジネスはなく、プロジェクトの他のサブシステムについて知る必要はないと思います。

可能な代替ソリューション

私が考えることができる 2 つの代替ソリューションが存在しますが、それらのそれぞれは、読みやすさと柔軟性、およびコンパイル時の複雑さと実行時の複雑さの通常のトレードオフを行います。

ミックスイン

ここに示されているように、テンプレートの巧妙さによってミックスインを使用すると、すべてではありませんが、MI アプローチの問題の一部を回避できます。その上、奇妙な継承階層が作成され、コンパイル時の複雑さが増します。また、階層Shapeにさらにクラスを追加するDrawableと、Rect.

来客パターン

ビジター パターンにより、オブジェクト階層のウォークを、それを操作するアルゴリズムから切り離すことができます。各オブジェクトは独自の型を認識しているため、そのアルゴリズムが何であるかを知らなくても、適切なアルゴリズムをディスパッチできます。ShapeCircleおよびを使用して説明するにはRect:

class Shape
{
    public:
        virtual void accept(class Visitor &v) = 0;
};

class Rect : public Shape
{
   public:
       float width;
       float height;

       void accept(class Visitor &v)
       {
           v.visit(this);
       }
};

class Circle : public Shape
{
    public:
        float radius;

       void accept(class Visitor &v)
       {
           v.visit(this);
       }
};

class Visitor
{
    public:
        virtual void visit(Rect *e) = 0;
        virtual void visit(Circle *e) = 0;
};

class ShapePainter : public Visitor
{
    // Provide graphics-related implementations for the two methods.
};

class ShapeSerializer : public Visitor
{
    // Provide methods to serialize each shape.
};

これを行うことで、実行時の複雑さをいくらか犠牲にしましたが、データからさまざまな懸念を切り離しました。プログラムに新しい懸念事項を追加するのは簡単になりました。必要なことは、次のように、この新しいクラスのオブジェクトと組み合わせて使用​​する別のクラスを追加することだけです。VisitorShape::accept()

class ShapeResizer : public Visitor
{
    // Resize Rect.
    // Resize Circle.
};

Shape *shapey = new Circle();
ShapeResizer sr;
shapey->accept(sr);

この設計パターンには、データとアルゴリズムの組み合わせを実装するのを忘れてプログラムで使用すると、コンパイラがエラーを出すという利点もあります。Shape::accept()後でオーバーライドして、たとえば のような集約形状タイプを定義したい場合がありますShapeStack。このようにして、スタック全体をトラバースして描画できます。

プロジェクトでパフォーマンスがそれほど重要でない場合は、Visitor ソリューションが優れていると思います。リアルタイムの制約を満たす必要があるが、締め切りに間に合うのを危険にさらすほどプログラムの速度が低下しない場合も、検討する価値があるかもしれません。

于 2013-06-01T21:50:41.763 に答える
2

Don't know if I understand your question correctly, but maybe policy based class design might be worth a look: http://en.wikipedia.org/wiki/Policy-based_design. It was first introduced in Alexandrescu's book Modern C++ Design. It allows to extend classes by deriving them from so called policies.

The mechanics looks like:

template<class DrawingPolicy> class Rectangle : public DrawingPolicy { ... };

where DrawingPolicy provides a draw(void) method for example, which is then available in class Rectangle.

Hope I could help

于 2013-06-01T21:39:29.030 に答える