Sie sollten entweder eine Schnittstelle (keine Vererbung) oder eine Art Eigenschaftsmechanik (möglicherweise sogar etwas, das wie das Ressourcenbeschreibungsframework funktioniert) verwenden.
Also entweder
interface Colored {
Color getColor();
}
class ColoredBook extends Book implements Colored {
...
}
oder
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 {
}
Klarstellung (bearbeiten):
Fügen Sie einfach optionale Felder in die Entitätsklasse ein
Vor allem mit dem Optional-Wrapper (edit: siehe 4castle -Antwort) halte ich dies (Hinzufügen von Feldern in der ursprünglichen Entität) für eine praktikable Möglichkeit, neue Eigenschaften in kleinem Maßstab hinzuzufügen. Das größte Problem bei diesem Ansatz ist, dass er möglicherweise gegen eine geringe Kopplung wirkt.
Stellen Sie sich vor, Ihre Buchklasse ist in einem speziellen Projekt für Ihr Domain-Modell definiert. Nun fügen Sie ein weiteres Projekt hinzu, das das Domänenmodell für eine spezielle Aufgabe verwendet. Diese Aufgabe erfordert eine zusätzliche Eigenschaft in der Buchklasse. Entweder haben Sie die Vererbung (siehe unten) oder Sie müssen das allgemeine Domänenmodell ändern, um die neue Aufgabe zu ermöglichen. In letzterem Fall kann es vorkommen, dass Sie eine Reihe von Projekten haben, die alle von ihren eigenen Eigenschaften abhängen, die der Buchklasse hinzugefügt wurden, während die Buchklasse selbst in gewisser Weise von diesen Projekten abhängt, da Sie die Buchklasse ohne die nicht verstehen können zusätzliche Projekte.
Warum ist die Vererbung problematisch, wenn es darum geht, zusätzliche Eigenschaften bereitzustellen?
Wenn ich Ihre Beispielklasse "Buch" sehe, denke ich an ein Domänenobjekt, das häufig viele optionale Felder und Untertypen enthält. Stellen Sie sich vor, Sie möchten eine Eigenschaft für Bücher hinzufügen, die eine CD enthalten. Es gibt jetzt vier Arten von Büchern: Bücher, Bücher mit Farbe, Bücher mit CD, Bücher mit Farbe und CD. Sie können diese Situation nicht mit Vererbung in Java beschreiben.
Mit Schnittstellen umgehen Sie dieses Problem. Sie können die Eigenschaften einer bestimmten Buchklasse einfach über Schnittstellen zusammenstellen. Durch Delegation und Komposition wird es einfach, genau die Klasse zu erhalten, die Sie möchten. Bei der Vererbung erhalten Sie normalerweise einige optionale Eigenschaften in der Klasse, die nur vorhanden sind, weil eine Geschwisterklasse sie benötigt.
Lesen Sie weiter, warum Vererbung oft eine problematische Idee ist:
Warum wird Vererbung von OOP-Befürwortern im Allgemeinen als eine schlechte Sache angesehen?
JavaWorld: Warum sich ausdehnt, ist böse
Das Problem beim Implementieren einer Reihe von Schnittstellen zum Erstellen einer Reihe von Eigenschaften
Wenn Sie Schnittstellen zur Erweiterung verwenden, ist alles in Ordnung, solange Sie nur einen kleinen Satz davon haben. Insbesondere wenn Ihr Objektmodell von anderen Entwicklern verwendet und erweitert wird, z. B. in Ihrem Unternehmen, wird die Anzahl der Schnittstellen zunehmen. Und schließlich erstellen Sie eine neue offizielle "Eigenschaftsschnittstelle", die eine Methode hinzufügt, die Ihre Kollegen bereits in ihrem Projekt für Kunde X für einen völlig unabhängigen Anwendungsfall verwendet haben - Ugh.
edit: Ein weiterer wichtiger Aspekt wird von gnasher729 erwähnt . Oft möchten Sie einem vorhandenen Objekt optionale Eigenschaften dynamisch hinzufügen. Mit Vererbung oder Schnittstellen müssten Sie das gesamte Objekt mit einer anderen Klasse neu erstellen, die alles andere als optional ist.
Wenn Sie Erweiterungen Ihres Objektmodells in einem solchen Ausmaß erwarten, sind Sie besser dran, wenn Sie explizit die Möglichkeit einer dynamischen Erweiterung modellieren . Ich schlage so etwas wie oben vor, wo jede "Erweiterung" (in diesem Fall Eigenschaft) eine eigene Klasse hat. Die Klasse fungiert als Namespace und Bezeichner für die Erweiterung. Wenn Sie die Paketnamenskonventionen entsprechend festlegen, werden auf diese Weise unendlich viele Erweiterungen ermöglicht, ohne dass der Namespace für Methoden in der ursprünglichen Entitätsklasse verschmutzt wird.
In der Spieleentwicklung stoßen Sie häufig auf Situationen, in denen Sie Verhalten und Daten in vielen Variationen zusammenstellen möchten. Aus diesem Grund wurde das Architekturmuster Entity-Component-System in Spielentwicklungskreisen sehr beliebt. Dies ist auch ein interessanter Ansatz, den Sie sich ansehen sollten, wenn Sie viele Erweiterungen für Ihr Objektmodell erwarten.