web-dev-qa-db-ja.com

保護されたメンバーのみを持つ抽象基本クラス

多くの場合、保護されたメンバーのみを持つ抽象基本クラスを作成して、クラスから共通ロジックを抽象化します。例えば:

_class Base {

protected:

    void foo() { ... }
    std::map<KeyType, ValueType> d_map;

};

class Derived : public Foo {

public:

    Derived() : Base() { ... }

    // ...

};
_

ここでの私のロジックは、foo()と_d_map_が複数の派生クラスでプライベートに使用される可能性があるため、この抽象化によってそれらが共通のベース表現に分解されます。

質問:このパターンは、厳密なデザインの観点から意味がありますか(それは良いスタイルと見なされますか)?パブリックにアクセス可能なメンバーとして保護された機能を備えた別のクラスを作成し、派生したサブクラスからそれらを参照することが望ましいでしょうか?

2
Ryan

2つ(またはそれ以上)の派生クラス間で実装を共有する方法として基本クラスを使用していて、基本クラスがインターフェイスを表していない場合、パブリック継承は間違いなく間違いです。

1つの可能性は、集約を使用することです(共有データをヘルパークラスに入れ、そのクラスのインスタンスを、それを必要とする各「派生」クラスに含めます)。

別の可能性は、プライベート継承を使用することです。プライベート継承は通常、「の観点から実装されている」と表現されます。パブリック継承がリスコフ代替原則(少なくともまともなデザイン)に準拠する必要がある場合、プライベート継承にはそのような要件はありません。プライベート継承を使用すると、派生クラスは基本クラスとの関係を「認識」し、基本クラスからの実装を使用できます。ただし、この関係は外部の世界からは「隠されている」ため、パブリック継承の場合と同様に、派生クラスから基本クラスへの暗黙的な変換notがあります。

class base { };

class D1 : public base { };

class D2 : base { };

int main() { 
    base *b = new D1; // no problem
    base *b2 = new D2; // won't compile
}

あなたが本当に主張するならば、あなたは派生へのポインターをベースへのポインターに変換することができます-しかしあなたはキャストを使わなければなりません(そして、興味深いことに、それはCスタイルのキャストでなければなりません;「新しい」C++キャストのどれも本当にこれを正しく行うことができます1)。


  1. これに異議を唱えたくなる人のために:はい、reinterpret_castは、一部のケースでは正しく機能するように見えますが、(1つの反例を示すために)複数の継承を使用する場合、派生型からベースタイプのoneに正しく変換されるだけです。別の基本型に変換しようとすると、悪い結果が生じます。
6
Jerry Coffin

まあ、クラスで公的にアクセス可能なデータを持つことは悪い習慣であり、確かに良いスタイルとは見なされていません。

いずれの場合も、そのクラスからプライベートに派生する必要があります。保護された派生は、場合によっては意味があります。

ただし、通常は代わりにコンポジションを使用するのが最善です。これにより、クラスはそれ自体でテスト可能になり(メソッドはパブリックになるため)、そのクラスは別の場所で使用できます。

また、基本クラスが既にあるクラスでその機能が必要な場合は、複数の継承を回避するのにも役立ちます。

クラス間の結合を少なくすることは常に良い考えです。パブリック派生はより強力なリンクであり、派生オブジェクトのみを使用する必要がありますIS基本オブジェクト。保護継承またはプライベート継承はそれほど結合されていませんが、構成はさらに優れています。

0
Phil1970

クラスのセマンティクスとの関連でメソッドのセマンティクスを知るまでは、それが良い習慣かどうかはわかりません。

基本クラスがAnimalであり、共有メソッドがEat、Talk、Poopなどの動物の行動を表す場合、OOの観点からは問題ありません。ただし、メソッドがいくつかの汎用の便利な機能である場合は電子メールを送信したり、文字列を処理したり、オブジェクトのテキスト表現を印刷したりするのと同じように、OO設計部門は不得意です。

0
Martin Maat

それはすべて、基本クラスを継承するエンティティのビジネスロジックに依存します。

それらすべてに共通の関数とフィールドがあり、異なる継承クラスでそれらの関数を異なるようにする特別なオーバーライドを行う必要がない場合、このスタイルは、SOLIDのLと一貫しているため、つまり リスコフの原理

ただし、継承されたエンティティのロジックが異なる場合、またはエンティティを共通の基本クラスでグループ化する場合は、エンティティにこの2つの共通点があり、他に何もないため、「ヘルパー」クラスが推奨されます。 Liskovの原則を破る の場合、一般的な引用があります。

それがアヒルのように見え、アヒルのようにガクガクしているが、バッテリーが必要な場合、おそらく間違った抽象化をしているでしょう。

0
civan