Dovresti usare un'interfaccia (non ereditaria) o un qualche tipo di meccanico di proprietà (forse anche qualcosa che funziona come il framework di descrizione delle risorse).
Quindi neanche
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):
Semplicemente aggiungendo campi opzionali nella classe entità
Soprattutto con il wrapper opzionale (modifica: vedi la risposta di 4castle ) Penso che questo (aggiungere campi nell'entità originale) sia un modo praticabile per aggiungere nuove proprietà su piccola scala. Il problema più grande con questo approccio è che potrebbe funzionare contro un 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 finisci con l'eredità (vedi sotto) o devi 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 loro proprietà aggiunte alla classe del libro mentre la classe del libro in un certo senso dipende 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 un modo per fornire proprietà aggiuntive?
Quando vedo la tua classe di esempio "Libro", penso a un oggetto di 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 ereditarietà in Java.
Con le interfacce aggiri questo problema. È possibile comporre facilmente le proprietà di una classe di libri specifica con interfacce. La delega e la composizione ti consentiranno di ottenere esattamente la classe che desideri. Con l'ereditarietà di solito si finiscono con alcune proprietà opzionali nella classe che sono lì solo perché una classe di pari livello ne ha bisogno.
Ulteriori letture sul perché l'eredità è spesso un'idea problematica:
Perché l'eredità è generalmente vista come una cosa negativa dai sostenitori di OOP
JavaWorld: Perché l'estensione è malvagia
Il problema con l'implementazione di una serie di interfacce per comporre una serie di proprietà
Quando usi le interfacce per l'estensione, tutto va bene purché tu ne abbia solo un piccolo set. Soprattutto quando il tuo modello a oggetti viene utilizzato ed esteso da altri sviluppatori, ad esempio nella tua azienda, la quantità di interfacce aumenterà. E alla fine si finisce per creare una nuova "interfaccia di proprietà" ufficiale che aggiunge un metodo già utilizzato dai colleghi nel loro progetto per il cliente X per un caso d'uso completamente non correlato: Ugh.
modifica: Un altro aspetto importante è menzionato da gnasher729 . Spesso si desidera aggiungere dinamicamente proprietà opzionali a un oggetto esistente. Con l'ereditarietà o le interfacce dovresti ricreare l'intero oggetto con un'altra classe che è tutt'altro che facoltativa.
Quando ti aspetti estensioni del tuo modello di oggetto a tal punto, starai meglio modellando esplicitamente la possibilità di estensione dinamica . Propongo qualcosa come sopra dove ogni "estensione" (in questo caso proprietà) ha la 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 inquinare lo spazio dei nomi per i metodi nella classe di entità originale.
Nello sviluppo del gioco si incontrano spesso situazioni in cui si desidera comporre comportamenti e dati in molte varianti. Questo è il motivo per cui l'architettura modello entità-componente-sistema è diventato molto popolare nei circoli di sviluppo del gioco. Questo è anche un approccio interessante che potresti voler guardare quando ti aspetti molte estensioni al tuo modello di oggetti.