継承とnull値の追加プロパティ


12

オプションのフィールドを持つクラスの場合、継承またはnull許容プロパティを使用する方が良いでしょうか?この例を考えてみましょう:

class Book {
    private String name;
}
class BookWithColor extends Book {
    private String color;
}

または

class Book {
    private String name;
    private String color; 
    //when this is null then it is "Book" otherwise "BookWithColor"
}

または

class Book {
    private String name;
    private Optional<String> color;
    //when isPresent() is false it is "Book" otherwise "BookWithColor"
}

これら3つのオプションに依存するコードは次のとおりです。

if (book instanceof BookWithColor) { ((BookWithColor)book).getColor(); }

または

if (book.getColor() != null) { book.getColor(); }

または

if (book.getColor().isPresent()) { book.getColor(); }

最初のアプローチは私にとってより自然に見えますが、キャストを行う必要があるため読みにくいかもしれません。この動作を達成する他の方法はありますか?


1
ソリューションは、ビジネスルールの定義に帰着するのではないかと考えています。つまり、すべての書籍に色が付いていても、その色がオプションである場合、すべての書籍に色のプロパティが存在することを知らせるため、継承は過剰で実際には不要です。一方、これまでに色について何も知らない本と色を持つ本の両方を持つことができれば、特別なクラスを作成することは悪くありません。その場合でも、おそらく継承よりも合成に行きます。
アンディ

少し具体的にするために-2種類の本があります-色のあるものとないものがあります。そのため、すべての本に色があるわけではありません。
Bojan VukasovicTest 16年

1
本に色がまったくない場合が本当にある場合、あなたの場合、継承はカラフルな本のプロパティを拡張する最も簡単な方法です。この場合、ベースブッククラスを継承し、それにカラープロパティを追加することで何かを壊すようには見えません。クラスの拡張が許可されている場合と許可されていない場合の詳細については、liskovの置換原則を参照してください。
アンディ

4
着色された本からあなたが望む行動を特定できますか?着色された本と着色されていない本の共通の動作を見つけることができますか?その場合、これは異なるタイプのOOPに適したケースであり、アイデアは、プロパティの存在と値を外部から問い合わせるのではなく、動作をクラスに移動することです。
エリックエイド16年

1
可能なブックの色が事前にわかっている場合、BookColor.NoColorのオプションを持つBookColorのEnumフィールドはどうですか?
グラハム

回答:


7

それは状況に依存します。BookWithColor実際のプログラムではサブクラスが呼び出されないため、特定の例は非現実的です。

ただし、一般的に、特定のサブクラスにのみ意味のあるプロパティは、それらのサブクラスにのみ存在する必要があります。

たとえば場合は、Book持っているPhysicalBookDigialBook子孫として、その後、PhysicalBook必要がある場合がありますweightプロパティ、およびプロパティを。しかし、その逆はありません。クラスにはすべての子孫が共有するプロパティのみを含める必要があるため、どちらのプロパティもありません。DigitalBooksizeInKbDigitalBookweightBook

より良い例は、実際のソフトウェアのクラスを見ることです。JSliderコンポーネントは、フィールドを持っていますmajorTickSpacing。スライダーにのみ「ティック」があるため、このフィールドはJSliderその子孫に対してのみ意味を持ちます。他の兄弟コンポーネントにフィールドJButtonがある場合、非常に混乱しmajorTickSpacingます。


または、両方を反映できるように重量を縮小することもできますが、おそらくそれは多すぎます。
ウォルフラット

3
@Walfrat:同じフィールドが異なるサブクラスの完全に異なるものを表すようにすることは、本当に悪い考えです。
ジャックB

本に物理版とデジタル版の両方がある場合はどうなりますか?各オプションフィールドの有無にかかわらず、すべての順列に対して異なるクラスを作成する必要があります。最良の解決策は、一部のフィールドを省略するか、本に依存しないようにすることです。2つのクラスが機能的に異なる振る舞いをする全く異なる状況を引用しました。それはこの質問が意味するものではありません。データ構造をモデル化するだけです。
4castle

@ 4castle:エディションごとに価格が異なる場合があるため、エディションごとに個別のクラスが必要になります。オプションのフィールドではこれを解決できません。しかし、色付きの本または「無色の本」に対してopが異なる動作を望んでいるかどうかは例からわかりません。そのため、この場合の正しいモデリングが何であるかを知ることは不可能です。
ジャックB

3
@JacquesBに同意します。「本をモデル化する」ための普遍的に正しいソリューションを提供することはできません。これは、解決しようとしている問題とビジネスの内容に依存するためです。liskovに違反するクラス階層を定義することは明確にWrong(tm)であると言うのはかなり安全だと思います(ええ、ええ、あなたのケースはおそらく非常に特別であり、それを保証します(疑いはありますが))
sara

5

言及されていないように見える重要な点:ほとんどの言語では、クラスのインスタンスは、どのクラスのインスタンスであるかを変更できません。したがって、色のない本があり、色を追加したい場合、異なるクラスを使用している場合は新しいオブジェクトを作成する必要があります。そして、おそらく古いオブジェクトへのすべての参照を新しいオブジェクトへの参照に置き換える必要があります。

「色のない本」と「色のある本」が同じクラスのインスタンスである場合、色を追加したり色を削除したりすることで問題が大幅に軽減されます。(ユーザーインターフェイスに「色のある本」と「色のない本」のリストが表示されている場合、そのユーザーインターフェイスは明らかに変更する必要がありますが、「赤のリスト」と同様に、書籍」および「グリーンブックのリスト」)。


これは本当に重要なポイントです!クラス属性ではなくオプションのプロパティのコレクションを考慮するかどうかを定義します。
クロマノイド

1

JavaBean(のようなBook)をデータベースのレコードと考えてください。オプションの列はnull、値がない場合ですが、完全に合法です。したがって、2番目のオプション:

class Book {
    private String name;
    private String color; // null when not applicable
}

最も合理的です。1

ただし、Optionalクラスの使用方法には注意してください。たとえば、それはSerializableJavaBeanの特徴であるでありません。Stephen Colebourneからのヒントを次に示します。

  1. タイプのインスタンス変数を宣言しないでくださいOptional
  2. nullクラスのプライベートスコープ内のオプションデータを示すために使用します。
  3. Optionalオプションのフィールドにアクセスするゲッターに使用します。
  4. Optionalセッターまたはコンストラクターで使用しないでください。
  5. Optionalオプションの結果を持つ他のビジネスロジックメソッドの戻り値の型として使用します。

したがって、クラス内でnullフィールドが存在しないことを表すために使用する必要がありますcolor Book(戻り値の型として)が離れるときは、フィールドでラップする必要がありOptionalます。

return Optional.ofNullable(color); // inside the class

book.getColor().orElse("No color"); // outside the class

これにより、明確な設計とより読みやすいコードが提供されます。


1のためにあなたがする場合BookWithColorしている図書の全体の「クラス」カプセル化する機能に特化他の本以上を、そしてそれは、使用の継承に意味をなさないと思います。


1
誰がデータベースについて何か言いましたか?すべてのオブジェクト指向パターンをリレーショナルデータベースのパラダイムに適合させる必要がある場合、多くのオブジェクト指向パターンを変更する必要があります。
アレクサンダーバード

「念のため」未使用のプロパティを追加することは、最も明確なオプションではないと主張します。他の人が言ったように、ユースケースに依存しますが、最も望ましい状況は、外部アプリケーションが存在するプロパティが実際に使用されているかどうかを知る必要があるプロパティを持ち越さないことです。
フォルカークーフェル16年

@Volker同意しないことに同意しOptionalます。これは、この正確な目的のためにJavaにクラスを追加する際に手を組んだ複数の人々を引用できるからです。オブジェクト指向ではないかもしれませんが、確かに有効な意見です。
4castle

@Alexanderbird私は特にシリアライズ可能なJavaBeanに取り組んでいた。JavaBeansを起動してトピックから外れていた場合、それは私の間違いでした。
4castle

@Alexanderbirdすべてのパターンがリレーショナルデータベースに一致するとは期待していませんが、Java Beanは
4castle

0

インターフェイス(継承ではない)または何らかの種類のプロパティメカニック(リソース記述フレームワークのように機能するものでさえ)を使用する必要があります。

それで

interface Colored {
  Color getColor();
}
class ColoredBook extends Book implements Colored {
  ...
}

または

class PropertiesHolder {
  <T> extends Property<?>> Optional<T> getProperty( Class<T> propertyClass ) { ... }
  <V, T extends Property<V>> Optional<V> getPropertyValue( Class<T> propertyClass ) { ... }
}
interface Property<T> {
  String getName();
  T getValue();
}
class ColorProperty implements Property<Color> {
  public Color getValue() { ... }
  public String getName() { return "color"; }
}
class Book extends PropertiesHolder {
}

明確化(編集):

エンティティクラスにオプションフィールドを追加するだけ

特にOptional-wrapperを使用すると(編集:4castleの回答を参照)、これ(元のエンティティにフィールドを追加する)は、小規模で新しいプロパティを追加する実行可能な方法だと思います。このアプローチの最大の問題は、低カップリングに対して機能する可能性があることです。

ブッククラスがドメインモデルの専用プロジェクトで定義されているとします。ここで、ドメインモデルを使用して特別なタスクを実行する別のプロジェクトを追加します。このタスクには、bookクラスの追加のプロパティが必要です。継承(以下を参照)になるか、新しいタスクを可能にするために共通ドメインモデルを変更する必要があります。後者の場合、ブッククラスに追加された独自のプロパティにすべて依存するプロジェクトの束になりますが、ブッククラス自体はこれらのプロジェクトに依存します。追加プロジェクト。

追加のプロパティを提供する方法になると、なぜ継承に問題があるのですか?

サンプルクラス「Book」を見ると、多くの場合、多くのオプションフィールドとサブタイプを持つドメインオブジェクトについて考えます。CDを含む本のプロパティを追加することを想像してください。現在、4種類の本があります。本、カラーの本、CDの本、カラーの本 CD。この状況をJavaの継承で記述することはできません。

インターフェイスを使用すると、この問題を回避できます。インターフェイスを使用して、特定のブッククラスのプロパティを簡単に構成できます。委任と構成により、希望するクラスを簡単に取得できます。継承を使用すると、通常、クラスにオプションのプロパティがいくつかありますが、それらは兄弟クラスが必要とするために存在します。

継承がしばしば問題の多いアイデアである理由をさらに読む:

継承が一般的にOOP支持者によって悪いものと見なされるのはなぜですか

JavaWorld:拡張が悪である理由

一連のインターフェースを実装して一連のプロパティを構成する際の問題

インターフェースを拡張に使用する場合、それらのごくわずかなセットがある限り、すべては問題ありません。特に、あなたのオブジェクトモデルが他の開発者によって使用され拡張されている場合、例えばあなたの会社でインターフェースの量は増えます。そして最終的に、まったく関係のないユースケースのために、同僚が顧客Xのプロジェクトですでに使用したメソッドを追加する新しい公式の「プロパティインターフェース」を作成することになります。

編集:別の重要な側面はgnasher729によって言及されています。多くの場合、既存のオブジェクトにオプションのプロパティを動的に追加する必要があります。継承またはインターフェイスを使用すると、オプションとはほど遠い別のクラスでオブジェクト全体を再作成する必要があります。

オブジェクトモデルの拡張をその程度まで期待する場合は、動的拡張の可能性を明示的にモデリングすることで改善できます。各「拡張」(この場合はプロパティ)が独自のクラスを持っている上記のようなものを提案します。クラスは、拡張機能の名前空間と識別子として機能します。パッケージの命名規則をそれに応じて設定すると、この方法により、元のエンティティクラスのメソッドのネームスペースを汚染することなく、無制限の拡張が可能になります。

ゲーム開発では、多くのバリエーションで動作とデータを作成したい状況にしばしば遭遇します。これが、アーキテクチャパターンentity-component-systemがゲーム開発サークルでかなり人気を得た理由です。これは、オブジェクトモデルに多くの拡張機能が期待される場合に、見ておきたい興味深いアプローチです。


そして、「これらのオプションをどのように決定するか」という質問には取り組んでいません。これは単なる別のオプションです。OPが提示したすべてのオプションよりも常に優れているのはなぜですか?
アレクサンダーバード

あなたは正しいです、私は答えを改善します。
クロマノイド

Javaインターフェースの@ 4castleは継承ではありません!インターフェイスを実装することは、基本的にクラスを継承しながらその動作を拡張する一方で、契約に準拠する方法です。1つのクラスで複数のクラスを拡張することはできませんが、複数のインターフェイスを実装できます。私の例では、継承を使用して元のエンティティの動作を継承しながら、インターフェイスで拡張します。
クロマノイド

理にかなっています。あなたの例でPropertiesHolderは、おそらく抽象クラスになります。しかし、getPropertyまたはのための実装は何を提案しますかgetPropertyValue?データは、どこかの何らかのインスタンスフィールドに保存する必要があります。またはCollection<Property>、フィールドを使用する代わりに、プロパティを保存するためにa を使用しますか?
4castle

1
私が作っBook拡張PropertiesHolder明確にするために。PropertiesHolder通常、この機能を必要とするすべてのクラスのメンバーである必要があります。実装は、Map<Class<? extends Property<?>>,Property<?>>またはこのようなものを保持します。これは実装のugい部分ですが、JAXBやJPAでも動作する本当に素晴らしいフレームワークを提供します。
クロマノイド

-1

Bookクラスが以下のようなカラープロパティなしで派生可能な場合

class E_Book extends Book{
   private String type;
}

継承は合理的です。


私はあなたの例を理解しているかわかりませんか?場合はcolorプライベートで、その後いかなる派生クラスはを気にする必要はありませんcolorとにかく。Book無視するコンストラクタをいくつか追加するだけで、colorデフォルトになりますnull
4castle
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.