tipos software programacion patrones orientado orientada objetos ejemplos donde diseño aplicaciones aplica oop design car-analogy

software - Problema de diseño OOP



programacion orientada a objetos y sus aplicaciones (9)

¿Qué es un buen diseño en este caso simple?

Digamos que tengo un coche de la clase base con un método FillTank(Fuel fuel) donde el combustible también es una clase base que tiene varias clases de hojas, diesel, etanol, etc.

En mi hoja de clase de automóviles DieselCar.FillTank(Fuel fuel) solo se permite cierto tipo de combustible (no hay sorpresas :)). Ahora aquí está mi preocupación, de acuerdo con mi interfaz, todos los autos pueden ser cargados con cualquier combustible, pero eso me parece incorrecto, en cada implementación de FillTank() verificar el combustible de entrada para el tipo correcto y si no arrojar error o algo así.

¿Cómo puedo rediseñar este caso a uno más preciso, incluso es posible? ¿Cómo diseñar un método base que tome una clase base para la entrada sin obtener estos "resultados extraños"?


Creo que el método aceptado sería tener un ValidFuel(Fuel f) en su clase base que arroje algún tipo de NotImplementedException (diferentes idiomas tienen diferentes términos) si los autos "hoja" no lo anulan.

FillTank podría entonces estar completamente en la clase base y llamar a ValidFuel para ver si es válido.

public class BaseCar { public bool ValidFuel(Fuel f) { throw new Exception("IMPLEMENT THIS FUNCTION!!!"); } public void FillTank(Fuel fuel) { if (!this.ValidFuel(fuel)) throw new Exception("Fuel type is not valid for this car."); // do what you''d do to fill the car } } public class DieselCar:BaseCar { public bool ValidFuel(Fuel f) { return f is DeiselFuel } }


En un sistema tipo CLOS, podrías hacer algo como esto:

(defclass vehicle () ()) (defclass fuel () ()) (defgeneric fill-tank (vehicle fuel)) (defmethod fill-tank ((v vehicle) (f fuel)) (format nil "Dude, you can''t put that kind of fuel in this car")) (defclass diesel-truck (vehicle) ()) (defclass normal-truck (vehicle) ()) (defclass diesel (fuel) ()) (defmethod fill-tank ((v diesel-truck) (f diesel)) (format nil "Glug glug"))

dándote este comportamiento:

CL> (fill-tank (make-instance ''normal-truck) (make-instance ''diesel)) "Dude, you can''t put that kind of fuel in this car" CL> (fill-tank (make-instance ''diesel-truck) (make-instance ''diesel)) "Glug glug"

Que, realmente, es la versión de Common stefaanv del doble despacho, como lo menciona stefaanv .


La programación orientada a objetos por sí sola no puede manejar bien este problema. Lo que necesita es programación genérica (la solución C ++ se muestra aquí):

template <class FuelType> class Car { public: void FillTank(FuelType fuel); };

Su automóvil diésel es solo un automóvil específico, Car<Diesel> .


Para esto se puede usar un despacho doble : acepte un poco de combustible antes de llenar. Tenga en cuenta que en un lenguaje que no lo admite directamente, introduce dependencias


Parece que solo quiere restringir el tipo de combustible que entra en su automóvil diesel. Algo como:

public class Fuel { public Fuel() { } } public class Diesel: Fuel { } public class Car<T> where T: Fuel { public Car() { } public void FillTank(T fuel) { } } public class DieselCar: Car<Diesel> { }

Haría el truco, por ejemplo

var car = new DieselCar(); car.FillTank(/* would expect Diesel fuel only */);

Esencialmente, lo que estás haciendo aquí es permitir que un Car tenga tipos de combustible específicos. También le permite crear un automóvil que sea compatible con cualquier tipo de Fuel (¡la posibilidad sería buena!). Sin embargo, en tu caso, el DieselCar, obtendrás una clase del automóvil y la restringirás a usar solo combustible Diesel .


Si existe un límite FillTank() entre los tipos de automóviles y los tipos de combustible, entonces FillTank() no tiene ningún negocio en la clase base de Car , ya que saber que tienes un automóvil no te dice qué tipo de combustible. Por lo tanto, para garantizar la corrección en tiempo de compilación, FillTank() debe definirse en las subclases y solo debe tomar la subclase Fuel que funcione.

Pero, ¿qué sucede si tiene un código común que no desea repetir entre las subclases? Luego, escribe un método FillingTank() protegido para la clase base a la que llama la función de la subclase. Lo mismo ocurre con Fuel .

Pero, ¿y si tienes un coche mágico que funciona con múltiples combustibles, por ejemplo, diésel o gas? Luego, ese auto se convierte en una subclase de DieselCar y GasCar y usted necesita asegurarse de que Car se declare como una superclase virtual para que no tenga dos instancias de Car en un objeto DualFuelCar . Llenar el tanque debería funcionar con poca o ninguna modificación: de manera predeterminada, obtendrá DualFuelCar.FillTank(GasFuel) y DualFuelCar.FillTank(DieselFuel) , lo que le dará una función sobrecargada por tipo.

Pero, ¿y si no quieres que la subclase tenga una función FillTank() ? Luego, debe pasar a la comprobación de tiempo de ejecución y hacer lo que pensó que tenía que hacer: hacer que la subclase controle Fuel.type y arroje una excepción o devuelva un código de error (prefiera el último) si hay una discrepancia. En C ++, RTTI y dynamic_cast<> son lo que recomendaría. En Python, isinstance() .


Use una clase base genérica (si su idioma lo admite (la siguiente es C #)):

public abstract class Car<TFuel> where TFuel : Fuel { public abstract void FillTank(TFuel fuel); }

Básicamente esto impone cualquier clase que hereda del automóvil para especificar qué tipo de combustible utiliza. Además, la clase Car impone una restricción de que TFuel debe ser algún subtipo de la clase abstracta de Fuel .

Digamos que tenemos una clase Diesel que es simple:

public class Diesel : Fuel { ... }

Y un automóvil que solo funciona con diesel:

public DieselCar : Car<Diesel> { public override void FillTank(Diesel fuel) { //perform diesel fuel logic here. } }


puedes ampliar tu interfaz original de coche

interface Car { drive(); } interface DieselCar extends Car { fillTank(Diesel fuel); } interface SolarCar extends Car { chargeBattery(Sun fuel); }

}


use el operador is para verificar las clases aceptadas, y puede lanzar una excepción en el constructor