web-dev-qa-db-ja.com

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

オプションのフィールドを持つクラスの場合、継承または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(); }

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

12

状況によります。実際のプログラムにはBookWithColorというサブクラスがないので、具体的な例は非現実的です。

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

たとえば、Bookに子孫としてPhysicalBookDigialBookがある場合、PhysicalBookにはweightプロパティがあり、DigitalBook a sizeInKbプロパティ。ただし、DigitalBookにはweightがありません。逆も同様です。 Bookにはどちらのプロパティもありません。これは、クラスにはすべての子孫が共有するプロパティのみが含まれる必要があるためです。

より良い例は、実際のソフトウェアのクラスを調べることです。 JSliderコンポーネントには、フィールドmajorTickSpacingがあります。スライダーだけに「目盛り」があるため、このフィールドはJSliderとその子孫に対してのみ意味があります。 JButtonのような他の兄弟コンポーネントにmajorTickSpacingフィールドがあると、非常に混乱します。

7
JacquesB

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

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

5
gnasher729

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

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

最も合理的です。1

Optional クラスの使用方法には注意してください。たとえば、通常はJavaBeanの特性であるSerializableではありません。 Stephen Colebourne からのヒントをいくつか紹介します。

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

したがって、within使用するクラスnullフィールドが存在しないことを表すが、colorleavesBook(戻り型として)になるときOptionalでラップする必要があります。

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

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

これにより、デザインが明確になり、コードが読みやすくなります。


1BookWithColorを使用して、他の本よりも特殊な機能を備えた本の「クラス」全体をカプセル化する場合は、、その後継承を使用することは理にかなっています。

1
4castle

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

だからどちらか

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を含む本のプロパティを追加することを想像してみてください。現在、本、色付きの本、CD付きの本、色付きの本およびの4種類の本があります。この状況をJavaの継承で説明することはできません。

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

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

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

JavaWorld:拡張が悪である理由

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

拡張機能にインターフェースを使用する場合、ごく一部のインターフェースさえあれば問題ありません。特に、オブジェクトモデルが他の開発者によって使用および拡張されている場合。あなたの会社ではインターフェースの数が増えるでしょう。そして最終的に、完全に無関係なユースケースである顧客Xのために同僚がプロジェクトで既に使用した方法を追加する新しい公式の「プロパティインターフェイス」を作成することになります-うーん。

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

オブジェクトモデルの拡張がこのような範囲になると予想される場合は、dynamic拡張の可能性を明示的にモデル化することで、より良い結果が得られます。上記のように、各 "拡張機能"(この場合はプロパティ)に独自のクラスがあることを提案します。クラスは、拡張の名前空間および識別子として機能します。パッケージの命名規則を適切に設定すると、このようにして、元のエンティティクラスのメソッドの名前空間を汚染することなく、無限の拡張が可能になります。

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

0
chromanoid