Enums o varias clases heredadas
Estoy leyendo sobre OOD y encontré un problema de diseño del estacionamiento. El estacionamiento tiene pisos de estacionamiento que tienen lugares de estacionamiento.
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);
}
}
Los parámetros se explican por sí mismos. Entonces, ¿por qué necesitamos clases separadas para cada valor ENUM? ¿Cuál es la ventaja de esto? Pensé en tener solo un tipo de clase ParkingSpot con un campo que representa el tipo de espacio de estacionamiento. Pero, ¿por qué debemos hacerlo? ¿Tiene clases separadas para cada valor ENUM junto con el almacenamiento de Enum como un campo también?
Nota: Para un diseño completo, consulte https://www.educative.io/courses/grokking-the-object-oriented-design-interview/gxM3gRxmr8Z
Respuestas
Tiene razón en que en este momento las clases derivadas son superfluas, porque su única diferencia es un valor de enumeración específico, que está etiquetado exactamente de la misma manera. Puede omitir la enumeración o las clases derivadas y nada cambiará.
Sin embargo, esa conclusión puede cambiar cuando hay más diferencias que solo el valor de enumeración.
Por ejemplo, si el precio de la plaza de aparcamiento se calcula de forma diferente en función del tipo de plaza. Al tener las clases derivadas, puede mantener la lógica de cálculo de cada tipo de lugar de estacionamiento por separado y hacer que anulen el mismo método base.
Comparativamente, si solo tuviera la clase base con la que trabajar, tendría que contener toda la lógica de cálculo.
Sin embargo, el ejemplo actual es un poco ligero en cuanto al contexto y la lista de características futuras para decidir si la herencia está garantizada aquí o no. No solo debemos evaluar el estado actual del código (en el que su observación es correcta), sino también tener en cuenta los cambios que se planean / esperan en el futuro cercano.
Dicho esto, sospecho que, en general, acabará teniendo que eliminar las clases derivadas o la enumeración. No veo el beneficio de implementar la misma información de dos formas diferentes.
La diferencia se hace evidente si consideramos
CompactSpot.Assign(Car.SUV)
obviamente, esto debería producir un error de algún tipo y tenemos dos opciones sobre cómo implementarlo.
ya sea:
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);
}
}
El enfoque de herencia nos permite dividir la lógica condicional entre múltiples clases en lugar de tener un bloque y una lógica condicional grandes en la clase base.
Si consumo su clase ParkingSpot como una dll binaria, puedo heredar un nuevo tipo de ParkingSpot y agregar mi propia lógica. Lo que no puedo hacer si tienes una enumeración
La pregunta se responde sola si pensamos en el comportamiento en lugar del estado, y deberíamos hacerlo. La herencia está motivada por "hacer lo mismo, pero de manera diferente" o por tener funcionalidad adicional para varios subtipos. Tal vez los diseñadores tienen una buena idea de que ParkingSpoty Vehicleel comportamiento se expandirá y evolucionar. Tal vez por el simple hecho de aprender, simplemente hornearon en subtipos.
Si bien un ENUM diferenciará los vehículos / parkingSpots del mismo tipo, los requisitos de funcionalidad deben ser una consideración primordial. Aun así, una enumeración puede resultar apropiada.
Es necesario hacer coincidir los vehículos y las plazas de aparcamiento; que sea un ENUM es simplemente un detalle de implementación. Entonces una VehicleTypepropiedad púbica parece encajar. Esta propiedad está mucho más en el espíritu de OO que, digamos, el código del cliente rebuscando en Reflection / metadata. El código del cliente no debe calcular el estado de otro objeto.
Pero debe haber algún servicio, digamos ParkingAllocator, que primero buscará un espacio libre relevante para un vehículo dado y solo si hay un espacio libre disponible de un vehículo en particular, estacionaremos un vehículo allí. un vehículo a un lugar, estamos absolutamente seguros de que el lugar es lo suficientemente grande para un vehículo. ¿Esto tiene sentido?
El diagrama de casos de uso muestra el asistente de estacionamiento y los actores del cliente. No están en el diagrama de clases, pero son parte del espacio del problema.
Creo que faltan requisitos o están defectuosos.
¿Se permite un vehículo más pequeño en una ranura más grande? Las motocicletas caben en todas partes.
Cualquier objeto del vehículo podría ser eléctrico o tener permisos para discapacitados o ambos, en la vida real de todos modos, así que veo
ElectricSlotyHandicapSlotcomo atributos de un lugar de estacionamiento. Como se muestra, el diagrama de clases aquí es inconsistente con el concepto de vehículos de diferentes tamaños que encajan en ranuras de diferentes tamaños.No existe una coincidencia aparente entre los tipos de lugares de estacionamiento y los tipos de vehículos. ¿Qué vehículo (s) requieren "grande", por ejemplo? De forma inmediata, un diccionario puede hacer coincidir explícitamente lugares con vehículos y / o viceversa. También los valores enteros de enumeración subyacentes pueden representar el tamaño relativo del lugar de estacionamiento. Y como dije anteriormente, "eléctrico" no es una talla.
¡Estructuras de datos!
estas cosas ...
if(car is SUV)
{
throw CarTooLarge()
}
base.assign(car);
}
desaparece si esta coincidencia de tipo de vehículo / tipo de lugar es una estructura de datos. A Dictionaryes una estructura de datos. De repente, veo dos diccionarios - vehículo-> spot y spot-> vehículo - como el núcleo de una clase con métodos apropiados.