オプションのフィールドを持つクラスの場合、継承または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(); }
最初のアプローチの方が自然に見えますが、キャストを行う必要があるため、読みにくいかもしれません。この動作を実現する他の方法はありますか?
状況によります。実際のプログラムにはBookWithColor
というサブクラスがないので、具体的な例は非現実的です。
ただし、一般に、特定のサブクラスに対してのみ意味を持つプロパティは、それらのサブクラスにのみ存在する必要があります。
たとえば、Book
に子孫としてPhysicalBook
とDigialBook
がある場合、PhysicalBook
にはweight
プロパティがあり、DigitalBook
a sizeInKb
プロパティ。ただし、DigitalBook
にはweight
がありません。逆も同様です。 Book
にはどちらのプロパティもありません。これは、クラスにはすべての子孫が共有するプロパティのみが含まれる必要があるためです。
より良い例は、実際のソフトウェアのクラスを調べることです。 JSlider
コンポーネントには、フィールドmajorTickSpacing
があります。スライダーだけに「目盛り」があるため、このフィールドはJSlider
とその子孫に対してのみ意味があります。 JButton
のような他の兄弟コンポーネントにmajorTickSpacing
フィールドがあると、非常に混乱します。
言及されていないように思われる重要なポイント:ほとんどの言語では、クラスのインスタンスはどのクラスのインスタンスであるかを変更することはできません。したがって、色のない本があり、色を追加したい場合、異なるクラスを使用している場合は、新しいオブジェクトを作成する必要があります。そして、おそらく、古いオブジェクトへのすべての参照を新しいオブジェクトへの参照に置き換える必要があります。
「色のない本」と「色のある本」が同じクラスのインスタンスである場合、色を追加したり、色を削除したりすることで問題が大幅に軽減されます。 (ユーザーインターフェースに「色付きの本」と「色なしの本」のリストが表示される場合、そのユーザーインターフェースは明らかに変更する必要がありますが、とにかく「赤のリスト」のように処理する必要があると思います本」と「グリーンブックのリスト」)。
JavaBean(Book
など)をデータベースのレコードと考えてください。オプションの列は、値がない場合はnull
ですが、完全に合法です。したがって、2番目のオプション:
class Book {
private String name;
private String color; // null when not applicable
}
最も合理的です。1
Optional
クラスの使用方法には注意してください。たとえば、通常はJavaBeanの特性であるSerializable
ではありません。 Stephen Colebourne からのヒントをいくつか紹介します。
- タイプ
Optional
のインスタンス変数は宣言しないでください。null
を使用して、クラスのプライベートスコープ内のオプションのデータを示します。- オプションのフィールドにアクセスするゲッターには
Optional
を使用します。- セッターまたはコンストラクターでは
Optional
を使用しないでください。- オプションの結果を持つその他のビジネスロジックメソッドの戻り値の型として
Optional
を使用します。
したがって、within使用するクラスnull
フィールドが存在しないことを表すが、color
leavesがBook
(戻り型として)になるときOptional
でラップする必要があります。
return Optional.ofNullable(color); // inside the class
book.getColor().orElse("No color"); // outside the class
これにより、デザインが明確になり、コードが読みやすくなります。
1BookWithColor
を使用して、他の本よりも特殊な機能を備えた本の「クラス」全体をカプセル化する場合は、、その後継承を使用することは理にかなっています。
インターフェース(継承ではない)またはある種のプロパティメカニック(リソース記述フレームワークのように機能するものでも)を使用する必要があります。
だからどちらか
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支持者]によって一般に悪いことと見なされているのはなぜですか
一連のプロパティを構成する一連のインターフェイスの実装に関する問題
拡張機能にインターフェースを使用する場合、ごく一部のインターフェースさえあれば問題ありません。特に、オブジェクトモデルが他の開発者によって使用および拡張されている場合。あなたの会社ではインターフェースの数が増えるでしょう。そして最終的に、完全に無関係なユースケースである顧客Xのために同僚がプロジェクトで既に使用した方法を追加する新しい公式の「プロパティインターフェイス」を作成することになります-うーん。
編集:別の重要な側面が gnasher729 で言及されています。多くの場合、オプションのプロパティを既存のオブジェクトに動的に追加します。継承またはインターフェースを使用すると、オプションではない別のクラスでオブジェクト全体を再作成する必要があります。
オブジェクトモデルの拡張がこのような範囲になると予想される場合は、dynamic拡張の可能性を明示的にモデル化することで、より良い結果が得られます。上記のように、各 "拡張機能"(この場合はプロパティ)に独自のクラスがあることを提案します。クラスは、拡張の名前空間および識別子として機能します。パッケージの命名規則を適切に設定すると、このようにして、元のエンティティクラスのメソッドの名前空間を汚染することなく、無限の拡張が可能になります。
ゲーム開発では、多くのバリエーションで動作やデータを作成したい状況に遭遇することがよくあります。これが、アーキテクチャパターン entity-component-system がゲーム開発のサークルでかなり人気を得た理由です。これは、オブジェクトモデルに多くの拡張機能が必要な場合に、注目したい興味深いアプローチでもあります。