Ereditarietà vs proprietà aggiuntive con valore null

13

Per le classi con campi facoltativi, è meglio utilizzare l'ereditarietà o una proprietà nullable? Considera questo esempio:

class Book {
    private String name;
}
class BookWithColor extends Book {
    private String color;
}

o

class Book {
    private String name;
    private String color; 
    //when this is null then it is "Book" otherwise "BookWithColor"
}

o

class Book {
    private String name;
    private Optional<String> color;
    //when isPresent() is false it is "Book" otherwise "BookWithColor"
}

Il codice che dipende da queste 3 opzioni sarebbe:

if (book instanceof BookWithColor) { ((BookWithColor)book).getColor(); }

o

if (book.getColor() != null) { book.getColor(); }

o

if (book.getColor().isPresent()) { book.getColor(); }

Il primo approccio mi sembra più naturale, ma forse è meno leggibile a causa della necessità di fare casting. C'è un altro modo per ottenere questo comportamento?

    
posta Bojan VukasovicTest 01.05.2016 - 22:23
fonte

5 risposte

7

Dipende dalle circostanze. L'esempio specifico non è realistico dal momento che non avresti una sottoclasse chiamata BookWithColor in un programma reale.

Ma in generale, una proprietà che ha senso solo per alcune sottoclassi dovrebbe esistere solo in quelle sottoclassi.

Ad esempio se Book ha PhysicalBook e DigialBook come discendenti, quindi PhysicalBook potrebbe avere una proprietà weight e DigitalBook a sizeInKb proprietà. Ma DigitalBook non avrà weight e viceversa. Book non avrà nessuna proprietà, dal momento che una classe dovrebbe avere solo proprietà condivise da tutti i discendenti.

Un esempio migliore è guardare le classi dal software reale. Il componente JSlider ha il campo majorTickSpacing . Poiché solo un cursore ha "tick", questo campo ha senso solo per JSlider e per i suoi discendenti. Sarebbe molto confuso se altri componenti di pari livello come JButton avessero un campo majorTickSpacing .

    
risposta data 02.05.2016 - 08:24
fonte
5

Un punto importante che non sembra essere stato menzionato: nella maggior parte delle lingue, le istanze di una classe non possono cambiare la classe di cui sono istanze. Quindi se hai un libro senza un colore e vuoi aggiungere un colore, devi creare un nuovo oggetto se stai usando classi diverse. E quindi probabilmente dovresti sostituire tutti i riferimenti al vecchio oggetto con riferimenti al nuovo oggetto.

Se "libri senza colore" e "libri con colore" sono istanze della stessa classe, l'aggiunta del colore o la rimozione del colore saranno meno problemi. (Se la tua interfaccia utente mostra un elenco di "libri con colore" e "libri senza colore", allora quell'interfaccia utente dovrebbe cambiare ovviamente, ma mi aspetto che sia qualcosa che devi comunque gestire, simile a un "elenco di rosso" libri "e" elenco di libri verdi ").

    
risposta data 03.05.2016 - 11:04
fonte
1

Pensa a un JavaBean (come il tuo Book ) come record in un database. Le colonne opzionali sono null quando non hanno valore, ma è perfettamente legale. Pertanto, la tua seconda opzione:

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

È il più ragionevole. 1

Attenzione però a come usi Optional classe. Ad esempio, non è Serializable , che di solito è una caratteristica di un JavaBean. Ecco alcuni suggerimenti da Stephen Colebourne :

  1. Do not declare any instance variable of type Optional.
  2. Use null to indicate optional data within the private scope of a class.
  3. Use Optional for getters that access the optional field.
  4. Do not use Optional in setters or constructors.
  5. Use Optional as a return type for any other business logic methods that have an optional result.

Pertanto, all'interno della tua classe dovresti usare null per rappresentare che il campo non è presente, ma quando color lascia Book (come tipo di ritorno) dovrebbe essere racchiuso con un Optional .

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

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

Questo fornisce un design chiaro e un codice più leggibile.

1 Se intendi un BookWithColor per incapsulare un'intera "classe" di libri che hanno capacità specializzate su altri libri, allora avrebbe senso usare l'ereditarietà.

    
risposta data 01.05.2016 - 23:57
fonte
0

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.

    
risposta data 02.05.2016 - 00:59
fonte
-1

Se la classe Book è derivabile senza la proprietà del colore come sotto

class E_Book extends Book{
   private String type;
}

quindi l'ereditarietà è ragionevole.

    
risposta data 01.05.2016 - 23:18
fonte

Leggi altre domande sui tag