多くの小さなクラスと論理的な(しかし)複雑な継承


24

優れたOOPデザイン、クリーンなコード、柔軟性、将来のコードのにおいを避けるという点で、何が優れているのだろうかと思っています。イメージの状況。クラスとして表す必要がある非常によく似たオブジェクトがたくさんあります。これらのクラスには特定の機能はなく、データクラスだけで、名前(およびコンテキスト)だけが異なります。例:

Class A
{
  String name;
  string description;
}

Class B
{
  String name;
  String count;
  String description;
}

Class C
{
  String name;
  String count;
  String description;
  String imageUrl;
}

Class D
{
  String name;
  String count;
}

Class E
{
  String name;
  String count;
  String imageUrl;
  String age;
}

「より良い」読みやすさを得るためにそれらを別々のクラスに保持する方が良いでしょうが、多くのコードの繰り返しがありますか、それとも継承を使用してよりDRYにする方が良いでしょうか?

継承を使用すると、次のような結果になりますが、クラス名は文脈上の意味を失います(has-aではなくis-aのため):

Class A
{
  String name;
  String description;
}

Class B : A
{
  String count;
}

Class C : B
{
  String imageUrl;
}

Class D : C
{
  String age;
}

継承はコードの再利用には使用されず、使用されるべきではありませんが、そのような場合にコードの繰り返しを減らす他の方法はありません。

回答:


58

一般的なルールは、「すべての継承を避ける」のではなく、「継承よりも委任を優先する」と読みます。オブジェクトに論理関係があり、Aが予想される場所であればどこでもBを使用できる場合は、継承を使用することをお勧めします。ただし、オブジェクトに同じ名前のフィールドがあり、ドメイン関係がない場合は、継承を使用しないでください。

かなり頻繁に、設計プロセスの「変更の理由」の質問をすることで結果が得られます。クラスAに余分なフィールドが追加された場合、クラスBにも追加フィールドが追加されると思いますか?それが当てはまる場合、オブジェクトは関係を共有し、継承は良い考えです。そうでない場合は、別個のコードで別個の概念を維持するために、少しの繰り返しに悩まされます。


確かに、変更の理由は良い点ですが、これらのクラスが常に一定である状況ではどうでしょうか?
user969153

20
@ user969153:クラスが本当に一定である場合、それは重要ではありません。すべての優れたソフトウェアエンジニアリングは、変更を行う必要がある将来のメンテナーのためのものです。将来の変更がない場合、すべてが行きますが、私を信頼してください、あなたがゴミ山のためにプログラムしない限り、あなたは常に変更を持ちます
チトン

@ user969153:言われているように、「変化の理由」は発見的です。決定するのに役立ちます。
チトン

2
いい答えだ。変更する理由は、優れたソフトウェアの設計に役立つ叔父ボブのSOLID頭字語のSです。
クレー

4
@ user969153、私の経験では、「これらのクラスはどこにあり、常に一定ですか?」有効なユースケースではありません:)まったく予期しない変更が発生しなかった、意味のあるサイズのアプリケーションでまだ作業していません。
cdkMoose

18

継承を使用すると、次のような結果になりますが、クラス名は文脈上の意味を失います(has-aではなくis-aのため):

まさに。コンテキスト外でこれを見てください:

Class D : C
{
    String age;
}

Dにはどのようなプロパティがありますか?覚えてる?階層をさらに複雑にした場合:

Class E : B
{
    int numberOfWheels;
}

Class F : E
{
    String favouriteColour;
}

そして、もし恐怖に恐怖があったら、EではなくFにimageUrlフィールドを追加したい場合はどうでしょうか?CとEから派生させることはできません。

多くの異なるコンポーネントタイプがすべて名前、ID、説明を持っている実際の状況を見てきました。しかし、これはコンポーネントであるという性質によるものではなく、単なる偶然でした。データベースに保存されているため、それぞれにIDがありました。名前と説明は表示用でした。

製品には、同様の理由でID、名前、説明もありました。しかし、それらはコンポーネントではなく、コンポーネント製品でもありませんでした。2つのことを一緒に見ることはありません。

しかし、一部の開発者はDRYについて読み、コードから重複のヒントをすべて削除することにしました。そこで彼はすべてを製品と呼び、それらのフィールドを定義する必要をなくしました。それらはProductで定義されており、これらのフィールドを必要とするすべてのものがProductから派生します。

これを十分に強く言うことはできません。これは良い考えではありません。

DRYは、共通のプロパティの必要性を取り除くことではありません。オブジェクトが製品ではない場合、製品と呼ばないでください。製品が説明である必要がない場合は、製品であるという性質上、説明を製品のプロパティとして定義しないでください。

最終的に、説明のないコンポーネントがありました。そのため、それらのオブジェクトにnullの説明が含まれるようになりました。NULL不可。ヌル。常に。タイプX ...以降のY ...およびNの製品の場合

これは、リスコフ代替原則の重大な違反です。

DRYとは、ある場所でバグを修正し、別の場所でバグを修正することを忘れる可能性のある場所に自分を置くことは決してないということです。同じプロパティを持つ2つのオブジェクトがあるため、これは起こりません。決して。心配しないでください。

クラスのコンテキストから、するべきことが明らかになった場合、これは非常にまれですが、そうして初めて共通の親クラスを検討する必要があります。ただし、プロパティの場合は、代わりにインターフェイスを使用しない理由を検討してください。


4

継承は、多態的な動作を実現する方法です。クラスに動作がない場合、継承は役に立ちません。この場合、オブジェクト/クラスではなく、構造を考慮します。

動作のさまざまな部分にさまざまな構造を配置できるようにしたい場合は、IHasName、IHasAgeなどのget / setメソッドまたはプロパティを持つインターフェイスを検討してください。一種のhiearchies。


3

これはインターフェースの目的ではありませんか?機能は異なるが、プロパティが類似している無関係なクラス。

IName = Interface(IInterface)
  function Getname : String;
  property Name : String read Getname
end;

Class Dog(InterfacedObject, IName)
private
  FName : String;
  Function GetName : String;
Public
  Name : Read Getname;
end;

Class Movie(InterfacedObject, IName)
private
  FCount;
  FName : String;
  Function GetName : String;
Public
  Name : String Read Getname;
  Count : read FCount; 
end;

1

あなたの質問は、多重継承をサポートしていないが、これを明確に指定していない言語の文脈で組み立てられているようです。多重継承をサポートする言語を使用している場合、単一レベルの継承のみでこれを簡単に解決できます。

以下は、多重継承を使用してこれを解決する方法の例です。

trait Nameable { String name; }
trait Describable { String description; }
trait Countable { Integer count; }
trait HasImageUrl { String imageUrl; }
trait Living { String age; }

class A : Nameable, Describable;
class B : Nameable, Describable, Countable;
class C : Nameable, Describable, Countable, HasImageUrl;
class D : Nameable, Countable;
class E : Nameable, Countable, HasImageUrl, Living;
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.