4

OK、私は C++ でやや複雑なシステムを持っています。簡単に言えば、サード パーティの抽象基本クラスにメソッドを追加する必要があります。サードパーティは、新しい機能を必要とする大量の派生クラスも提供しています。

標準の Shape インターフェースといくつかの一般的な形状を提供するライブラリを使用しています。

class Shape
{
    public:
        Shape(position);
        virtual ~Shape();

        virtual position GetPosition() const;
        virtual void SetPosition(position);

        virtual double GetPerimeter() const = 0;

    private: ...
};

class Square : public Shape
{
    public:
        Square(position, side_length);
    ...
};

class Circle, Rectangle, Hexagon, etc

さて、ここに私の問題があります。Shape クラスにも GetArea() 関数を含めたいと思います。だから私はただやるべきだと思う:

class ImprovedShape : public virtual Shape
{
    virtual double GetArea() const = 0;
};

class ImprovedSquare : public Square, public ImprovedShape
{
    ...
}

次に、ImprovedShape と Square を継承する ImprovementSquare を作成します。さて、ご覧のとおり、恐ろしいダイヤモンドの継承問題を作成しました。これは、サード パーティのライブラリが Square や Circle などに仮想継承を使用している場合、簡単に修正できます。しかし、それを実行させるのは合理的な選択肢ではありません。

では、ライブラリで定義されたインターフェイスにちょっとした機能を追加する必要がある場合はどうすればよいでしょうか? 良い答えはありますか?

ありがとう!

4

10 に答える 10

7

このクラスが形状から派生する必要があるのはなぜですか?

class ImprovedShape : public virtual Shape
{
    virtual double GetArea() const = 0;
};

なぜ持っていないのですか

class ThingWithArea 
{
    virtual double GetArea() const = 0;
};

ImprovementSquare は Shape であり、ThingWithArea です

于 2008-10-30T09:33:33.830 に答える
4

ファサードパターンうまくいくはずです。

サードパーティ インターフェイスを独自のインターフェイスにラップすると、アプリケーションのコードは、サードパーティ インターフェイスではなくラッパー インターフェイスで動作します。そうすれば、制御されていないサード パーティ インターフェイスの変更も適切に分離されます。

于 2008-10-30T07:44:36.267 に答える
4

おそらく、適切な継承について読んで、ImprovedShape は Shape から継承する必要はなく、代わりにその描画機能に Shape を使用できると結論付ける必要があります。 from List 同じ機能を提供したい場合でも、単純に List を使用できます。

同様に、ImprovedShape はShape を使用して Shape の処理を​​行うことができます。

于 2008-10-30T09:48:44.863 に答える
4

プロジェクトで非常によく似た問題があり、Shape から改良されたShape を派生させないことで解決しました。ImprovementShape で Shape 機能が必要な場合は、キャストが常に機能することがわかっているため、dynamic_cast を実行できます。残りはあなたの例と同じです。

于 2008-10-30T07:52:35.580 に答える
2

おそらくデコレータパターンの使用ですか?[ http://en.wikipedia.org/wiki/Decorator_pattern][1]

于 2008-10-30T09:58:48.197 に答える
1

デイブ・ヒリアーのアプローチは正しいものです。GetArea()独自のインターフェイスに分離します。

class ThingWithArea
{
public:
    virtual double GetArea() const = 0;
};

Shapeの設計者が正しいことを行い、それを純粋なインターフェイスにし、具象クラスのパブリックインターフェースが十分に強力である場合、具象クラスのインスタンスをメンバーとして持つことができます。これはあなたがSquareWithAreaImprovedSquare悪い名前です)aShapeThingWithArea:になる方法です

class SquareWithArea : public Shape, public ThingWithArea
{
public:
    double GetPerimeter() const { return square.GetPerimeter(); }
    double GetArea() const { /* do stuff with square */ }

private:
    Square square;
};

残念ながら、Shape設計者はいくつかの実装をに入れており、最初に提案したダイヤモンドのように、Shapeごとに2つのコピーを実行することになります。SquareWithArea

これにより、最も緊密に結合された、したがって最も望ましくないソリューションになります。

class SquareWithArea : public Square, public ThingWithArea
{
};

最近では、C++の具象クラスから派生するのは悪い形だと考えられています。なぜそうすべきでないのか、本当に良い説明を見つけるのは難しいです。operator=()通常、人々はマイヤーズのより効果的なC ++アイテム33を引用します。これは、とりわけまともなものを書くことが不可能であることを指摘しています。したがって、おそらく、値のセマンティクスを持つクラスに対しては決してそれを行うべきではありません。もう1つの落とし穴は、具象クラスに仮想デストラクタがない場合です(これが、STLコンテナから公に派生してはならない理由です)。ここではどちらも当てはまりません。継承について学ぶためにあなたをC++のよくある質問に見下すように送ったポスターは間違っています-追加GetArea()リスコフの置換可能性に違反しません。私が見ることができる唯一のリスクは、実装者が後で名前を変更してコードを黙って壊したときに、具象クラスの仮想関数をオーバーライドすることから生じます。

要約すると、あなたは明確な良心を持ってスクエアから導き出すことができると思います。(慰めとして、Shapeインターフェースのすべての転送関数を作成する必要はありません)。

次に、両方のインターフェースを必要とする関数の問題について説明します。不要なものは好きではありませんdynamic_cast。代わりに、関数に両方のインターフェイスへの参照を取得させ、呼び出しサイトで両方の同じオブジェクトへの参照を渡します。

void PrintPerimeterAndArea(const Shape& s, const ThingWithArea& a)
{
    cout << s.GetPerimeter() << endl;
    cout << a.GetArea() << endl;
}

// ...

SquareWithArea swa;
PrintPerimeterAndArea(swa, swa);

その仕事をするために必要なのPrintPerimeterAndArea()は、境界の源と面積の源です。これらがたまたま同じオブジェクトインスタンスのメンバー関数として実装されていることは問題ではありません。おそらく、その領域は、それとの間の数値積分エンジンによって供給される可能性がありShapeます。

これにより、一方の参照を渡し、もう一方の参照を取得することを検討する唯一のケースにたどり着きdynamic_castます。ここでは、2つの参照が同じオブジェクトインスタンスを参照していることが重要です。これは非常に工夫された例です:

void hardcopy(const Shape& s, const ThingWithArea& a)
{
    Printer p;
    if (p.HasEnoughInk(a.GetArea()))
    {
        s.print(p);
    }
}

それでも、私はおそらく、ではなく2つの参照を送信したいと思い dynamic_castます。このような関数に2つの異なるインスタンスのビットが供給される可能性を排除するために、私は健全な全体的なシステム設計に依存します。

于 2008-11-02T11:21:38.430 に答える
1

メタプログラミング/ミックスインの別のテイク。今回はトレイトの影響を少し受けています。面積の計算は、公開されたプロパティに基づいて追加するものであると想定しています。モジュール化ではなく、カプセル化を維持した何かを行うことができます。それが目標です。ただし、可能であれば多態的なものを使用するのではなく、すべてのサブタイプに対して GetArea を記述する必要があります。それが価値があるかどうかは、カプセル化にどれだけコミットしているか、および以下のRectangularShapeのような一般的な動作を利用できる基本クラスがライブラリにあるかどうかによって異なります。

#import <iostream>

using namespace std;

// base types
class Shape {
    public:
        Shape () {}
        virtual ~Shape () { }
        virtual void DoShapyStuff () const = 0;
};

class RectangularShape : public Shape {
    public:
        RectangularShape () { }

        virtual double GetHeight () const = 0 ;
        virtual double GetWidth  () const = 0 ;
};

class Square : public RectangularShape {
    public:
        Square () { }

        virtual void DoShapyStuff () const
        {
            cout << "I\'m a square." << endl;
        }

        virtual double GetHeight () const { return 10.0; }
        virtual double GetWidth  () const { return 10.0; }
};

class Rect : public RectangularShape {
    public:
        Rect () { }

        virtual void DoShapyStuff () const
        {
            cout << "I\'m a rectangle." << endl;
        }

        virtual double GetHeight () const { return 9.0; }
        virtual double GetWidth  () const { return 16.0; }
};

// extension has a cast to Shape rather than extending Shape
class HasArea {
    public:
        virtual double GetArea () const = 0;
        virtual Shape& AsShape () = 0;
        virtual const Shape& AsShape () const = 0;

        operator Shape& ()
        {
            return AsShape();
        }

        operator const Shape& () const
        {
            return AsShape();
        }
};

template<class S> struct AreaOf { };

// you have to have the declaration before the ShapeWithArea 
// template if you want to use polymorphic behaviour, which 
// is a bit clunky
static double GetArea (const RectangularShape& shape)
{
    return shape.GetWidth() * shape.GetHeight();
}

template <class S>
class ShapeWithArea : public S, public HasArea {
    public:
        virtual double GetArea () const
        {
            return ::GetArea(*this);
        }
        virtual Shape& AsShape ()             { return *this; }
        virtual const Shape& AsShape () const { return *this; }
};

// don't have to write two implementations of GetArea
// as we use the GetArea for the super type
typedef ShapeWithArea<Square> ImprovedSquare;
typedef ShapeWithArea<Rect> ImprovedRect;

void Demo (const HasArea& hasArea)
{
    const Shape& shape(hasArea);
    shape.DoShapyStuff();
    cout << "Area = " << hasArea.GetArea() << endl;
}

int main ()
{
    ImprovedSquare square;
    ImprovedRect   rect;

    Demo(square);
    Demo(rect);

    return 0;
}
于 2008-11-02T12:47:49.707 に答える
1

テンプレートとメタプログラミング手法を使用して、まったく異なるアプローチを行うことは可能ですか? テンプレートを使用しないという制約がない場合、これは洗練されたソリューションを提供する可能性があります。変更のみImprovedShape:ImprovedSquare

template <typename ShapePolicy>
class ImprovedShape : public ShapePolicy
{
public:
    virtual double GetArea();
    ImprovedShape(void);
    virtual ~ImprovedShape(void);

protected:
    ShapePolicy shape;
    //...
};

となり、次のようにImprovedSquareなります。

class ImprovedSquare : public ImprovedShape<Square>
{
public:
    ImprovedSquare(void);
    ~ImprovedSquare(void);

    // ...

};

ひし形の継承を回避し、元の Shape からの継承 (ポリシー クラスによる) と必要な追加機能の両方を取得します。

于 2008-10-30T14:52:12.673 に答える
1

GetArea() はメンバーである必要はありません。テンプレート化された関数である可能性があるため、任意の形状に対して呼び出すことができます。

何かのようなもの:

template <class ShapeType, class AreaFunctor> 
int GetArea(const ShapeType& shape, AreaFunctor func);

STL minmax関数は、あなたのケースのアナロジーと考えることができます。コンパレータ関数を指定すると、オブジェクトの配列/ベクトルの最小値と最大値を見つけることができます。同様に、面積を計算する関数があれば、任意の形状の面積を導出できます。

于 2010-09-29T16:40:27.410 に答える
0

私が質問を理解したように、あなたの問題には解決策があります。addapter-patternを使用します。アダプター パターンは、特定のクラスに機能を追加したり、特定の動作(つまりメソッド) を交換したりするために使用されます。あなたが描いたシナリオを考えると:

class ShapeWithArea : public Shape
{
 protected:
  Shape* shape_;

 public:
  virtual ~ShapeWithArea();

  virtual position GetPosition() const { return shape_->GetPosition(); }
  virtual void SetPosition(position)   { shape_->SetPosition(); }
  virtual double GetPerimeter() const  { return shape_->GetPerimeter(); }

  ShapeWithArea (Shape* shape) : shape_(shape) {}

  virtual double getArea (void) const = 0;
};

Adapter-Pattern は、クラスの動作または機能を適応させることを目的としています。あなたはそれを使用することができます

  • メソッドを転送するのではなく、再実装することによって、クラスの動作を変更します。
  • メソッドを追加して、クラスに動作を追加します。

それはどのように行動を変えますか?base 型のオブジェクトをメソッドに提供すると、適合したクラスも提供できます。オブジェクトは指示どおりに動作し、オブジェクトのアクターは基本クラスのインターフェイスのみを気にします。このアダプターは、Shape の任意の派生物に適用できます。

于 2008-11-19T12:21:38.763 に答える