13

具体的な があるとしましょうclass Appleclass Peach(Apple オブジェクトはインスタンス化できます。) さて、誰かがやって来て、Apple から抽象を導き出します。新しい純粋仮想関数を導入するため、抽象的です。Peach のユーザーは、Peach から派生させて、この新しい関数を定義することを余儀なくされています。これはよくあるパターンですか?これは正しいですか?

サンプル:


class Apple
{
public:
    virtual void MakePie();
    // more stuff here
};

class Peach : public Apple { public: virtual void MakeDeliciousDesserts() = 0; // more stuff here };

ここで、具体的な があるとしましょうclass Berryclass Tomato誰かがベリーから要約を導き出します。これは、Berry の仮想関数の 1 つを上書きして純粋な仮想関数にするため、抽象的です。Tomato のユーザーは、Berry で以前に定義された関数を再実装する必要があります。これはよくあるパターンですか?これは正しいですか?

サンプル:


class Berry
{
public:
    virtual void EatYummyPie();
    // more stuff here
};

class Tomato : public Berry { public: virtual void EatYummyPie() = 0; // more stuff here };

注: 名前は考案されたものであり、実際のコードを反映していません (うまくいけば)。この質問の執筆において、果物は害を受けていません。

4

7 に答える 7

8

アップルのピーチ氏:

  • Apple が値クラスである場合は、これを行わないでください (つまり、コピー ctor がある、同一でないインスタンスが等しい場合など)。理由については、Meyers のより効果的な C++ 項目 33 を参照してください。
  • Apple がパブリックな非仮想デストラクタを持っている場合は、これを行わないでください。そうしないと、ユーザーが Peach へのポインタを介して Apple を削除したときに、未定義の動作を招くことになります。
  • それ以外の場合は、 Liskov の代入可能性に違反していないため、おそらく安全です。桃はリンゴです。
  • Apple コードを所有している場合は、共通の抽象基本クラス (Fruit など) を取り出して、そこから Apple と Peach を派生させることをお勧めします。

Re Tomato from ベリー:

  • 上記に加えて:
  • 珍しいから避ける
  • 必要であれば、Tomato の派生クラスが Liskov の代入可能性に違反しないために何をしなければならないかを文書化してください。Berry でオーバーライドしている関数 (と呼びましょう) は、Juice()特定の要件を課し、特定の約束をします。の派生クラスの実装はJuice()、これ以上要求してはなりません。次に、DerivedTomato IS-A Berry と、Berry についてのみ知っているコードは安全です。

おそらく、DerivedTomatoes が を呼び出す必要があることを文書化することで、最後の要件を満たすことができますBerry::Juice()。その場合は、代わりにテンプレート メソッドを使用することを検討してください。

class Tomato : public Berry
{
public:
    void Juice() 
    {
        PrepareJuice();
        Berry::Juice();
    }
    virtual void PrepareJuice() = 0;
};

植物学的な予想に反して、トマトがベリーである可能性は非常に高いです。(例外は、派生クラスの の実装が、PrepareJuiceによって課されるものを超える追加の前提条件を課す場合ですBerry::Juice)。

于 2008-11-21T23:01:50.297 に答える
5

悪いデザインの兆候のように思えます。閉じたライブラリから具体的な定義を取得して拡張し、そこから多くのものを分岐したい場合は強制される可能性がありますが、その時点で、継承よりもカプセル化に関するガイドラインを真剣に検討しています..カプセル化できる場合、あなたはおそらくすべきです。

ええ、考えれば考えるほど、これは非常に悪い考えです。

于 2008-11-21T22:23:54.180 に答える
3

継承モデルを「is-a」にするという推奨される方法を使用する場合、このパターンはほとんど発生しません。

具象クラスができたら、それは実際にインスタンスを作成できるものだと言っています。そこから抽象クラスを派生させると、基本クラスの属性である何かが派生クラスには当てはまりません。派生クラスは、何かが正しくないというクラクションを設定する必要があります。

あなたの例を見ると、桃はリンゴではないので、桃から派生するべきではありません。ベリー由来のトマトも同様です。

これは私が通常封じ込めを勧める場所ですが、リンゴには桃が含まれていないため、それは良いモデルではないようです.

この場合、共通のインターフェイスである PieFilling または DessertItem を除外します。

于 2008-11-21T22:53:38.240 に答える
2

必ずしも間違っているわけではありませんが、間違いなく臭いです。特に果物を長時間太陽の下に置いた場合. (そして、私の歯科医は私にコンクリートのリンゴを食べさせたがらないと思います。)

ただし、ここで私が目にする主な臭いは、具象クラスから派生した抽象クラスではなく、非常に深い継承階層です。

編集:再読すると、これらは2つの階層であることがわかります。果物のすべてが私を混乱させました。

于 2008-11-21T22:26:39.977 に答える
1

少し珍しいですが、基本クラスの他のサブクラスがあり、抽象クラスのサブクラスに、次のような抽象クラスの存在を正当化するのに十分な共通のものがある場合:

class Concrete
{
public:
    virtual void eat() {}
};
class Sub::public Concrete { // some concrete subclass
    virtual void eat() {}
};
class Abstract:public Concrete // abstract subclass
{
public:
    virtual void eat()=0;
    // and some stuff common to Sub1 and Sub2
};
class Sub1:public Abstract {
    void eat() {}
};
class Sub2:public Abstract {
    void eat() {}
};
int main() {
    Concrete *sub1=new Sub1(),*sub2=new Sub2();
    sub1->eat();
    sub2->eat();
    return 0;
}
于 2008-11-21T23:18:54.803 に答える
0

うーん...「なんて...」と数秒間考えてみると、それは一般的ではないという結論に達しました...また、アップルからピーチを、ベリーからトマトを導き出すことはありません...もっと良い例はありますか?:)

C++ でできる奇妙なことはたくさんあります...その 1% も考えられません...

仮想を純粋な仮想でオーバーライドすることについては、おそらくそれを非表示にすることができ、それは本当に奇妙になります...

この関数を仮想関数としてリンクする愚かな C++ コンパイラを見つけることができれば、ランタイムの純粋な仮想関数呼び出しが得られます...

これはハッキングに対してのみ実行できると思いますが、実際にどのようなハッキングが行われたのかわかりません...

于 2008-11-21T22:31:30.653 に答える
0

最初の質問に答えるために、これを行うことができます。なぜなら、Apple のユーザーは、Peach から派生した具体的なインスタンスが与えられた場合、違いを知らないからです。また、インスタンスは、それが Apple ではないことを認識しません (Apple から提供された、オーバーライドされた、あなたが教えてくれなかった仮想関数がいくつかある場合を除きます)。

仮想関数を純粋な仮想関数でオーバーライドすることがどれほど役立つか、まだ想像できません-それは合法ですか?

一般に、Scott Meyers の「すべての非リーフ クラスを抽象化する」項目に準拠したいと考えています。

とにかく、あなたが説明していることは合法であるように思われることを除けば、あなたがそれを頻繁に必要としているとは思えないだけです。

于 2008-11-21T22:33:26.237 に答える