継承が間違っている


12

良い継承モデルが下り坂になっているコードがいくつかあり、それを修正する理由と方法を理解しようとしています。基本的に、次のようなZoo階層があるとします。

class Animal  
class Parrot : Animal 
class Elephant : Animal 
class Cow : Animal

eat()、run()などのメソッドがあり、すべてが適切です。それからある日誰かがやって来て言う-私たちのCageBuilderクラスはうまく機能し、animal.weight()とanimal.height()を使用しますが、新しいアフリカバイソンは強すぎて壁を砕くことができるので、追加しますAnimalクラスのもう1つのプロパティ-isAfricanBizon()で、マテリアルを選択するときにそれを使用し、AfricanBizonクラスに対してのみオーバーライドします。次の人が来て、似たようなことをします。次に、基本クラスへの階層のサブセットに固有のこれらすべてのプロパティがあることを知っています。

そのようなコードを改善/リファクタリングする良い方法は何ですか?ここでの1つの代替方法は、dynamic_castsを使用して型をチェックすることですが、呼び出し元を混乱させ、あらゆる場所にif-then-elseを追加します。ここでは、より具体的なインターフェイスを使用できますが、基本クラス参照のみを使用している場合は、あまり役に立ちません。他の提案はありますか?例?

ありがとう!


@James:パーサーを手書きで作成する必要があります。:S
マッテオイタリア

5
明らかに、これはばかげた顧客要件の場合です。アフリカにはバイソンはいません。現実とは関係のないオブジェクトモデルを設計することはできません。その現実がドルでいっぱいの手によって作成されない限り。問題を解決します。
ハンスPassant

1
バイソンをすべて食べる?[以前にこれを投稿しましたが、何らかの理由で、おそらくユーモアのないジャッカスによって削除されました。]
ジェームズマクネリス

CageBuilderには独自のクラスが必要ですか?個々のクラスごとにオーバーライドできるデフォルトのMakeCageメソッドがある場合はどうなりますか。
ジョブ

1
if-then-elseクラッターは呼び出し側の欠点として言及していますが、呼び出し側がisAfricanBizon()の使用を開始するとすぐに、if-then-elseでコードが自動的に散乱します。したがって、isAfricanBizon()を使用したif-then-elseクラッターか、動的キャストを使用したif-then-elseクラッターのいずれかです。
davidk01

回答:


13

この問題は、RequiresConcreteWall()を実装する代わりに、IsAfricanBison()を呼び出すフラグを実装し、壁をクラスのスコープ外で変更するかどうかのロジックを移動したようです。クラスは、アイデンティティではなく動作と要件を公開する必要があります。これらのクラスの消費者は、彼らが何であるかに基づいてではなく、彼らが言われたものから働くべきです。


1
-1:してはいけないことだけを言います。OPはこれが悪い考えであることをすでに知っているため、質問です。
スティーブンエバーズ

12

isAfricanBizon()はジェネリックではありません。また、強すぎる飼育動物飼育場で動物農場を拡張し、isAfricanBizon()からtrueを返して適切な効果を得るのはばかげているとします。

特定の質問に答えるメソッドを常にインターフェイスに追加したい場合、この場合はstrength()のようなものになります


+1:この特定のユースケースに対応するために、クラスの概念モデル(さまざまな種類の動物のプロパティをカプセル化するだけ)を壊しているようです。strengthこの方法は、によって照会することができるmaterial.canHold(animal)材料の異なる種類以上をサポートするクリーンな方法を可能にしますConcreteWall
エイダンカリー

strength()プロパティのアプローチは、RequiresConcreteWall()の他の提案よりも優れています。これは、将来の要件を有効にするための柔軟性が高いためです。まず最初に、CageBuilderクラスに、どのマテリアルが十分に強いかを決定させてください。そうすれば、新しいマテリアルで簡単にクラスを拡張できます。
11

3

あなたの問題はこれだと思います:階層のサブセットのみに関心があるが、基本クラスへのポインター/参照が渡されるライブラリのさまざまなクライアントがあります。それは実際にdynamic_cast <>が解決する問題です。

dynamic_cast <>の使用を最小限に抑えることは、クライアントの設計の問題です。オブジェクトを特別な処理が必要かどうかを判断し、必要であれば、ダウンキャストされた参照に対するすべての操作を行う必要があります。

いくつかの個別のサブ階層に適用される機能の「ミックスイン」タイプのコレクションがある場合、JavaとC#が使用するインターフェイスパターンを使用できます。純粋仮想クラスである仮想基本クラスを持ち、dynamic_cast <>を使用して、インスタンスがその実装を提供するかどうかを判断します。


1

できることの1つは、タイプの明示的なisAfricanBison()チェックを、実際に興味のあるプロパティのチェックに置き換えることですisTooStrong()


1
isTooStrong()何のために?動物クラスにケージ固有のコードを追加しています。
スティーブンエバーズ

1

動物はコンクリートの壁を気にするべきではありません。たぶん、単純な値で表現できます。

class Animal {
public:
  virtual ~Animal() {}
  virtual size_t height() const = 0;
  virtual size_t weight() const = 0;
  virtual bool isStrong() const = 0;
};

Cage *CreateCageFromSQL(Animal &a);
Cage *CreateCageFromOrangePeelsAndSticks(Animal &a);

しかし、それは現実的ではないと思います。しかし、それはおもちゃの例の問題です。

RequiresConcreteWalls()や、動的ポインターキャストの行と行は、決して見たくないでしょう。

これは通常、安価なソリューションです。保守と概念化は簡単です。そして本当に、問題はとにかく動物の種類に結びついていると述べています。

class Animal {
public:
  virtual ~Animal() {}
  virtual CageBuilder *getCageBuilder() = 0;
};

これは、共有コードの使用を妨げるものではなく、Animalを少し汚染するだけです。

しかし、ケージがどのように構築されるかは、他のシステムのポリシーかもしれません。また、動物ごとに複数のタイプのケージビルダーがあるかもしれません。あなたが思い付くことができる多くの奇妙で複雑な組み合わせがあります。

私はコンポーネントベースデザインを良い目的に使用しましたが、その主な問題は、Animalの所有権が共有されると面倒になる可能性があることです。デストラクタをスローポイントにしないようにする方法。

ダブルディスパッチも別のオプションですが、私は常にそれに飛び込むことをticしていました。

それを超えて、問題を推測することは困難です。


0

確かに、すべての動物には固有の特性がありattemptEscape()ます。メソッドの中にはfalse、すべてのシナリオで結果をもたらすものもあれば、などの他の固有の特性の経験則に基づいたチャンスがあるものもsizeありweightます。それから確かにattemptEscape()戻ってくるので、確かにある時点で些細になりtrueます。

私はあなたの質問を完全に理解していないのではないかと心配しています...すべての動物には関連する行動と特徴があります。その動物に特有のものを、適切な場所に導入する必要があります。BisonをParrotsに直接関連付けようとすることは、適切なセットアップではなく、適切な設計では問題になりません。


-1

別のオプションは、各動物に適したケージを作成する工場を使用することです。これは、それぞれの条件が非常に異なる場合に改善できると思います。しかし、これがこの1つの条件である場合は、上記のRequiresConcreteWall()メソッドが実行します。


-1

RequiresConcreteWall()とは異なり、RecommendCageType()はどうですか


-2

こんなことしてみませんか

class Animals { /***/ } class HeavyAnimals{} : Animals //The basic class for animals like the African Bison

HeavyAnimalsクラスでは、HeavyAnimalsクラスを拡張することにより、African Bisonクラスを作成できます。

これで、HeavyAnimalクラスなどの他の基本クラスを作成するために使用できる親クラス(動物)を使用して、African Bisonクラスおよび他の重い動物を作成できます。アフリカのバイソンを使用すると、Animalクラスのメソッドとプロパティ(これはすべての動物のベース)にアクセスでき、HeavyAnimalsクラス(これはHeavy Animalsのベース)にアクセスできます。


2
それはミックスインまたはトレイトとして機能しますが、サブクラスとしては機能しません。これは、次に別のプロパティが必要になったときに、複数の継承を求めています。
オーダス
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.