Struttura di ereditarietà per consentire tipi sconosciuti

2

Diciamo che ho un client che sta consumando Vehicle oggetti inviati da un server.

Ho due tipi di veicoli: Car e Truck . Alcuni Car s possono essere inHybridMode ma non tutti. Il server attualmente fornisce tre modelli specifici in un elenco: Prius , Camaro e F150 . Nel client ho creato questa gerarchia:

abstract class Vehicle
    String name;

abstract class Car extends Vehicle

abstract class Truck extends Vehicle

interface Hybrid
    boolean isInHybridMode()    

class Prius extends Car, implements Hybrid

class Camaro extends Car

class F150 extends Truck

Ho scelto di creare Hybrid un'interfaccia in modo che la struttura di ereditarietà non sia pazzesca (e super disordinata se mai hai un camion ibrido).

Il client può facilmente creare questi veicoli dall'uscita del server (diciamo JSON). Controllo semplicemente il campo type e creo la classe concreta appropriata.

[
   {
      "name":"bob's car",
      "type":"Prius",
      "inHybridMode":true
   },
   {
      "name":"joe's car",
      "type":"Camaro"
   }
]

La mia domanda è: come posso gestire la situazione in cui il server mi restituisce un veicolo per il quale non ho (ancora) una lezione concreta?

{
   "name":"alice's car",
   "type":"Camry",
   "inHybridMode":true
}

Le mie opzioni sono:

  1. Ignora completamente questo tipo e non creare un oggetto. Ciò causerà problemi quando cerco di fare vehicles.size() .

  2. Crea Vehicle (e Car e Truck ) non astratto e crea solo un oggetto Vehicle con il corrispondente campo name

  3. Crea una nuova classe chiamata UnknownHybrid extends Car, implements Hybrid . Questo fungerà da catch-all per qualsiasi veicolo di tipo sconosciuto e ha il campo inHybridMode .

  4. Crash orribilmente, costringendomi a implementare una classe Camry .

Mi piace il # 3 mentre questo problema è semplice, ma non voglio creare catch-alls per ogni combinazione di interfacce che creerò in seguito.

UPDATE:

Innanzitutto, grazie per tutte le ottime risposte.

Nel tentativo di semplificare il mio problema per la domanda, ho lasciato alcune cose che hai identificato. Risponderò a queste domande:

  1. In realtà ho type e model . Il tipo può essere solo Car o Truck. Camry, Camaro, F150, ecc. È il modello.

  2. Ho solo bisogno del comportamento di auto, camion e ibridi. Non esiste una cosa come il comportamento di Camry, solo in quanto è un comportamento Car + Hybrid.

    Ho aggiunto le classi del modello (Camry, F150, ecc.) in modo da avere una classe in grado di implementare l'interfaccia Hybrid . Io penso avrò meno modelli di combinazioni di interfacce. Alla fine avrò un altro comportamento "mix in" come Convertible , 4WD , ecc. Quindi ho pensato di creare solo classi per ogni modello invece di ogni combinazione di interfaccia (HybridConvertibleCar, ecc.)

  3. Per quanto riguarda i requisiti aziendali: visualizzare informazioni su Car, visualizzare le informazioni su Truck, visualizzare l'indicatore verde in modalità ibrida, gestire modelli sconosciuti che possono essere o meno ibridi, convertibili, 4wd.

    La cosa che sto cercando di evitare (e l'implementazione corrente) è che tutto il comportamento per ibrido, convertibile, 4wd è tutto contenuto nella classe Vehicle . Quindi ho molti modelli che hanno campi / metodi che non li riguardano. Ad esempio, Truck ha un metodo isConvertibleTopDown che restituisce sempre "false".

  4. C'è di più in Hybrid rispetto a isInHybridMode. Ne ho appena rimosso la maggior parte per rispondere alla domanda.

  5. Se tutto ciò che avevo era Vehicle visualizzerei solo dati specifici del veicolo (ad esempio name ).

posta tir38 10.09.2016 - 05:22
fonte

5 risposte

9

Non confondere i dati con datatype.

Se il campo type non è vincolato ai tipi di dati già presenti, non è un campo per il tipo di dati. Sono solo dati.

Inoltre, perché ti interessa che sia un Camry ? Quale comportamento diverso verrebbe inviato in modo polimorfico? Sposta i dati di Camry in un tipo di dati Car perché vuoi Car di comportamento con questi dati. Se esiste un comportamento come Camry non lo si otterrà da questo json. Lo otterrai definendo una classe Camry .

Per quanto riguarda le opzioni, non mi interessa cosa pensa il server che mi sta dando. Mi interessa quello che sto cercando di fare con esso.

Se il mio lavoro è quello di visualizzare un piccolo indicatore luminoso quando in modalità ibrida non faccio una cagata su Camaro, Car o Joe. Mi interessa se siamo inHybridMode . Se chiamo quel metodo solo per trovarlo, non esisterò in un'eccezione che genera umore, quindi è meglio che esista.

Se il mio lavoro è quello di visualizzare il nome del veicolo, non mi interessa se si tratta di un camion. Mi interessa che abbia un nome.

Tra le opzioni 1, 2, 3 e 4, posso immaginare i motivi per farlo tutte. Queste ragioni sono centrate su ciò che stai realmente cercando di fare con ciò che il server ti sta consegnando. Le cose che stai facendo decidono che cosa è una buona idea qui. I dati non dettano i tuoi casi d'uso. Determina solo quali dati puoi agire.

Stai provando a costruire le tue classi dai dati. Questo è solo all'indietro. Costruisci dai tuoi bisogni e prendi i dati che ti sono utili.

L'unico bisogno che ho identificato qui ho indovinato dalle classi, che è anche al contrario. Se vuoi una risposta migliore, abbiamo bisogno di una domanda migliore. Cosa dovrebbe fare questo?

    
risposta data 10.09.2016 - 06:51
fonte
1

Sembra che il lato server e il lato client non siano sviluppati in tandem. Se lo fossero, lanciare un'eccezione sarebbe appropriato. Ma stai cercando di mappare il tuo modello al modello degli sviluppatori di server che non conosci.

Questa è la programmazione di un bersaglio mobile. Questo si sta muovendo nell'oscurità. Buona fortuna.

Se inHybridMode è l'unico membro di un veicolo ibrido, non creerei un'interfaccia per questo. È solo una proprietà, non un comportamento, quindi una proprietà booleana in più sulla classe Vehicle sarebbe molto più semplice e robusta. Sarebbe un po '"appiattito" al tuo modello, ma risolverebbe il tuo problema, che vorresti essere pronto per l'imprevisto. Vuoi un veicolo universale che si adatta a tutto ciò che il server ti getta, una classe unica per tutti che può sopravvivere a un piccolo ritocco sul lato server.

    
risposta data 10.09.2016 - 08:08
fonte
1

Forse dovresti fare un passo indietro dal problema concreto e pensare a OOAD in generale.

In termini generali: nel tuo programma stai costruendo un model di qualcosa. Il things di modello e il modo in cui modelli queste cose dipende completamente da te. È come inventare un gioco, in cui imposti le regole. Se questo gioco ha un "senso", vale a dire se serva a qualsiasi scopo pratico e quanto sia stato bello raggiungere questo scopo con il tuo software è uno degli aspetti, che rende la qualità del nostro software.

E un modo (popolare) di costruire modelli è OOP . Una forza di OOP in quanto tale sta creando gruppi astratti di objects tramite inheritance .

Se una object eredita da un'altra ha in qualche modo una relazione o similarità che condivide con altri membri del gruppo.

Questo è il cosiddetto is-a -relationship, dove si forma un gruppo di oggetti. Lingue come Java, C # ecc. Consentono due tipi di ereditarietà: class inheritance e interface inheritance , cioè una cosa is come un'altra o behaves come un altro oggetto.

Se ho oggetti simili, ad es. car avrebbe senso modellarli come class inheritance . Se ho oggetti, che condividono un'abilità comune - in grado di volare - ha senso, modellarli come interface di ereditarietà.

Ciò che rende OOP così utile è raggruppare insieme oggetti eterogenei e trattarli allo stesso modo. Ad esempio: potresti dire a una collezione di Oggetti, che sono in grado di volare , solo per fare quel .fly() e independentemente come riescono a farlo, sei capace per parlare con ciascuno di essi.

Da quanto detto sopra sei in grado di capire che la tua domanda suona un po 'strana:

Inheritence structure to allow unknown types

Questo in qualche modo contraddice ciò che stai cercando di ottenere con OOP . Certo, se hai a che fare con oggetti eterogenei non puoi determinare esattamente cosa fa, ma sai in che modo questo comportamento sarebbe conosciuto , dal momento che tu (si spera) modellato i gruppi in questo modo.

My question is, how can I handle the situation where the server returns me a vehicle that I don't (yet) have a concrete class for?

Se mai dovessi finire in una situazione del genere, sai, che il tuo modello è semplicemente sbagliato .

Se prendo il tuo esempio di Trucks e Cars e being hybrid - indipendentemente, penso che tu abbia modellato correttamente:

Qualunque cosa il tuo front-end sia in grado di modellare deve essere modellato anche nel back-end. Supponi che i tuoi modelli di frontend siano un hybrid Truck, il backend deve tradurre questo in una chiamata a factory , che sa come gestirlo .

Se non vi è alcuna possibilità di "costruire un'auto" come se fosse "ordinato", il risultato è un comportamento eccezionale .

Rispondi: correggi il tuo modello del mondo.

My options are: ...

La correzione sarebbe quella di costruire un'astrazione migliore . Secondo il tuo modello, avrebbe senso avere una fabbrica astratta, restituirti una fabbrica di cemento, restituirti un oggetto concreto; cioè una fabbrica di veicoli che produce truckfactory o carfactory , che a sua volta produce concrete (non -) veicoli ibridi del loro genere.

Per dare un consiglio più concreto, ho bisogno di informazioni più dettagliate, su ciò che vuoi ottenere.

    
risposta data 10.09.2016 - 15:15
fonte
1

Puoi fare qualcosa del genere? Solo una classe, nessuna eredità.

class Vehicle
    String name;
    String type;
    Optional<Boolean> isInHybridMode;

Qui Optional è il tipo di opzione della tua lingua.

    
risposta data 10.09.2016 - 15:35
fonte
1

Gerarchia di classi e amp; l'ereditarietà è un sistema di modellazione limitato (che, btw, varia tra le lingue) inteso specificamente per la modellazione e l'elaborazione del codice, non necessariamente per la modellazione di tutti gli oggetti del mondo reale.

Quindi, non tentare di creare una classe per ogni & ogni entità del mondo reale. Inoltre, non modellare ciò di cui non hai bisogno.

Utilizza le istanze in più e le classi in meno.

Utilizza una nuova classe solo quando hai bisogno di comportamenti differenziati. Altrimenti usa una classe già esistente.

Considera l'aggiunta di uno stato alla classe per personalizzare un'istanza, soprattutto se questo stato si applica a tutte le istanze, prima di considerare l'aggiunta di una nuova sottoclasse. Non è possibile utilizzare una sottoclasse per ogni attributo possibile, ad esempio solo l'anno del modello o la posizione di produzione.

Refactor in modo che la gerarchia delle classi sia semplice e ti consenta di creare istanze che soddisfano le tue esigenze (di business computing).

    
risposta data 10.09.2016 - 17:31
fonte

Leggi altre domande sui tag