Dovresti utilizzare un'interfaccia (non l'ereditarietà) o un qualche tipo di meccanica delle proprietà (forse anche qualcosa che funzioni come il framework di descrizione delle risorse).
Quindi,
interface Colored {
Color getColor();
}
class ColoredBook extends Book implements Colored {
...
}
o
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 {
}
Chiarimento (modifica):
Semplice aggiunta di campi facoltativi nella classe di entità
Specialmente con il wrapper facoltativo (modifica: vedere la risposta 4castle ) Penso che questo (aggiungendo i campi nell'entità originale) sia un modo fattibile per aggiungere nuove proprietà su piccola scala. Il più grande problema con questo approccio è che potrebbe funzionare contro l'accoppiamento basso.
Immagina che la tua classe di libri sia definita in un progetto dedicato per il tuo modello di dominio. Ora aggiungi un altro progetto che utilizza il modello di dominio per eseguire un'attività speciale. Questa attività richiede una proprietà aggiuntiva nella classe del libro. O si finisce con l'ereditarietà (vedi sotto) o si deve cambiare il modello di dominio comune per rendere possibile la nuova attività. In quest'ultimo caso potresti finire con un mucchio di progetti che dipendono tutti dalle proprie proprietà aggiunte alla classe del libro mentre la classe del libro stesso dipende in un certo modo da questi progetti, perché non sei in grado di capire la classe del libro senza il progetti aggiuntivi.
Perché l'ereditarietà è problematica quando si tratta di fornire proprietà aggiuntive?
Quando vedo la tua classe di esempio "Book", penso a un oggetto dominio che spesso finisce per avere molti campi e sottotipi opzionali. Immagina di voler aggiungere una proprietà per i libri che includono un CD. Ora ci sono quattro tipi di libri: libri, libri con colori, libri con CD, libri con colori e CD. Non è possibile descrivere questa situazione con l'ereditarietà in Java.
Con le interfacce elimini questo problema. È possibile comporre facilmente le proprietà di una classe di libri specifica con interfacce. Delegazione e composizione renderanno facile ottenere esattamente la classe che desideri. Con l'ereditarietà di solito finisci con alcune proprietà opzionali nella classe che sono presenti solo perché una classe di pari livello ha bisogno di loro.
Leggendo ulteriormente perché l'ereditarietà è spesso un'idea problematica:
Perché l'ereditarietà generalmente considerato come una brutta cosa dai sostenitori di OOP
JavaWorld: Why extends is evil
Il problema con l'implementazione di un set di interfacce per comporre un insieme di proprietà
Quando usi le interfacce per l'estensione tutto va bene finché ne hai solo una piccola serie. Soprattutto quando il tuo modello di oggetti viene utilizzato ed esteso da altri sviluppatori, ad es. nella tua azienda crescerà la quantità di interfacce. E alla fine si finisce per creare una nuova "interfaccia di proprietà" ufficiale che aggiunge un metodo che i tuoi colleghi hanno già utilizzato nel loro progetto per il cliente X per un caso d'uso completamente non correlato - Ugh.
modifica: un altro aspetto importante è citato da gnasher729 . Spesso si desidera aggiungere in modo dinamico proprietà facoltative a un oggetto esistente. Con l'ereditarietà o le interfacce dovresti ricreare l'intero oggetto con un'altra classe che è lontana dall'essere opzionale.
Quando ti aspetti che le estensioni del tuo modello a oggetti siano tali da farti star meglio, modellando esplicitamente la possibilità dell'estensione dinamica . Propongo qualcosa come sopra in cui ogni "estensione" (in questo caso la proprietà) ha una sua classe. La classe funziona come spazio dei nomi e identificatore per l'estensione. Quando si impostano le convenzioni di denominazione dei pacchetti di conseguenza, in questo modo si consente una quantità infinita di estensioni senza contaminare lo spazio dei nomi per i metodi nella classe di entità originale.
Nello sviluppo del gioco spesso incontri situazioni in cui desideri comporre comportamenti e dati in molte varianti. Questo è il motivo per cui il modello di architettura entity-component-system è diventato molto popolare nelle cerchie di sviluppo del gioco. Questo è anche un approccio interessante che potresti voler vedere, quando ti aspetti molte estensioni al tuo modello di oggetto.