Enumerazioni o classi ereditate multiple

Sep 27 2020

Sto leggendo di OOD e mi sono imbattuto in un problema di progettazione del parcheggio. Il parcheggio ha piani di parcheggio con posti auto.

public enum ParkingSpotType {
  HANDICAPPED, COMPACT, LARGE, MOTORBIKE, ELECTRIC
}
public abstract class ParkingSpot {
  private String number;
  private boolean free;
  private Vehicle vehicle;
  private final ParkingSpotType type;

  public boolean IsFree();

  public ParkingSpot(ParkingSpotType type) {
    this.type = type;
  }

  public boolean assignVehicle(Vehicle vehicle) {
    this.vehicle = vehicle;
    free = false;
  }

  public boolean removeVehicle() {
    this.vehicle = null;
    free = true;
  }
}

public class HandicappedSpot extends ParkingSpot {
  public HandicappedSpot() {
    super(ParkingSpotType.HANDICAPPED);
  }
}

public class CompactSpot extends ParkingSpot {
  public CompactSpot() {
    super(ParkingSpotType.COMPACT);
  }
}

public class LargeSpot extends ParkingSpot {
  public LargeSpot() {
    super(ParkingSpotType.LARGE);
  }
}

public class MotorbikeSpot extends ParkingSpot {
  public MotorbikeSpot() {
    super(ParkingSpotType.MOTORBIKE);
  }
}

public class ElectricSpot extends ParkingSpot {
  public ElectricSpot() {
    super(ParkingSpotType.ELECTRIC);
  }
}

I parametri sono autoesplicativi.Quindi ecco perché abbiamo bisogno di classi separate per ogni valore ENUM? Qual è il vantaggio di questo? Ho pensato di avere un solo tipo di classe ParkingSpot con un campo che rappresenta il tipo di slot di parcheggio.Ma perché dobbiamo farlo hanno classi separate per ogni valore ENUM insieme alla memorizzazione di Enum come campo?

Nota: per la progettazione completa, fare riferimento https://www.educative.io/courses/grokking-the-object-oriented-design-interview/gxM3gRxmr8Z

Risposte

3 Flater Oct 27 2020 at 18:55

Hai ragione sul fatto che in questo momento le classi derivate sono superflue, perché la loro unica differenza è un valore enum specifico, che è etichettato nello stesso modo. È possibile omettere l'enumerazione o le classi derivate e non cambierebbe nulla.

Tuttavia, tale conclusione potrebbe cambiare quando sono presenti più differenze oltre al valore enum.

Ad esempio, se il prezzo del parcheggio è calcolato in modo diverso in base al tipo di posto. Avendo le classi derivate, è possibile mantenere la logica di calcolo di ogni tipo di parcheggio separatamente e fare in modo che sostituiscano lo stesso metodo di base.
Comparativamente, se avessi solo la classe base con cui lavorare, dovrebbe contenere tutta la logica di calcolo.

Tuttavia, l'esempio attuale è un po 'chiaro sul contesto e sull'elenco delle funzionalità future per decidere se l'eredità è garantita o meno. Non dovremmo solo valutare lo stato attuale del codice (in cui la tua osservazione è corretta), ma anche tenere conto di eventuali modifiche pianificate / previste nel prossimo futuro.


Detto questo, sospetto che in genere finirai per dover rimuovere le classi derivate o l'enumerazione. Non vedo il vantaggio di implementare le stesse informazioni in due modi diversi.

Ewan Sep 27 2020 at 16:50

La differenza diventa evidente se consideriamo

CompactSpot.Assign(Car.SUV)

questo dovrebbe ovviamente produrre un errore di qualche tipo e abbiamo due scelte su come implementarlo.

o:

ParkingSpot.Assign(car)
{
    if(car is SUV && this.type = COMPACT)
    {
        throw CarTooLarge()
    }
    if(car != HANDICAPPED && this.type == HANDICAPPED)
    ....
}

CompactSpot : ParkingSpot
    Assign(car)
    {
        if(car is SUV)
        {
            throw CarTooLarge()
        }
        base.assign(car);
    }
}

L'approccio dell'ereditarietà ci consente di dividere la logica condizionale tra più classi anziché avere un blocco condizionale e una logica di grandi dimensioni nella classe di base.

Se utilizzo la tua classe ParkingSpot come dll binaria, posso ereditare un nuovo tipo di ParkingSpot e aggiungere la mia logica. Cosa che non posso fare se hai un enum

radarbob Oct 29 2020 at 09:20

La domanda risponde da sola se pensiamo al comportamento invece che allo stato, e dovremmo. L'ereditarietà è motivata dal "fare la stessa cosa, ma in modo diverso" o dall'avere funzionalità aggiuntive per vari sottotipi. Forse i progettisti hanno una buona idea che ParkingSpote il Vehiclecomportamento si espanderà ed evolverà. Forse per il gusto di imparare hanno semplicemente inserito la sottotitolazione.

Mentre un ENUM differenzia veicoli / parcheggi dello stesso tipo, i requisiti per la funzionalità dovrebbero essere una considerazione prioritaria. Anche così, un enum potrebbe comunque essere appropriato.

È necessario abbinare veicoli e parcheggi: il fatto che si tratti di un ENUM è semplicemente un dettaglio di implementazione. Quindi una VehicleTypeproprietà pubica sembra adattarsi. Questa proprietà è molto più nello spirito di OO che, diciamo, il codice del client che rovista attraverso Reflection / metadata. Il codice client non deve calcolare lo stato di un altro oggetto.


Ma deve esserci un servizio, diciamo ParkingAllocator che cercherà prima uno slot libero pertinente per un dato veicolo e solo se è disponibile uno slot libero di un particolare veicolo, parcheggeremo un veicolo lì. Quindi, se stiamo cercando di assegnare un veicolo in un punto, siamo assolutamente sicuri che lo spot sia abbastanza grande per il veicolo. Ha senso ciò?

Il diagramma del caso d'uso mostra l'addetto al parcheggio e gli attori del cliente. Non sono nel diagramma delle classi ma fanno parte dello spazio del problema.


Penso che ci siano requisiti mancanti o difettosi.

  • È consentito un veicolo più piccolo in uno slot più grande? Le motociclette si adattano ovunque.

  • Qualsiasi oggetto veicolo potrebbe essere elettrico o avere permessi di handicap o entrambi - nella vita reale comunque - quindi vedo ElectricSlot& HandicapSlotcome attributi di un parcheggio. Come mostrato, il diagramma delle classi qui non è coerente con il concetto di veicoli di dimensioni diverse che si adattano a slot di dimensioni diverse.

  • Non esiste una corrispondenza apparente tra i tipi di parcheggio e i tipi di veicoli. Quali veicoli richiedono "grandi", ad esempio? Un dizionario può abbinare esplicitamente i punti ai veicoli e / o viceversa. Anche i valori interi enum sottostanti possono rappresentare la dimensione relativa del parcheggio. E come ho detto sopra, "elettrico" non è una taglia.


Strutture dati!

questa roba ...

if(car is SUV)
    {
        throw CarTooLarge()
    }
    base.assign(car);
}

scompare se questa corrispondenza del tipo di veicolo / tipo di spot è una struttura di dati. A Dictionaryè una struttura dati. A prima vista, vedo due dizionari - veicolo-> spot e spot-> veicolo - come il nucleo di una classe con metodi appropriati.