2009-04-16 10 views
5

Por ejemplo supongamos que tengo un vehículo de clase y deseo para un ConvertibleVehicle subclase que tiene métodos adicionales, tales como foldRoof(), turboMode(), foldFrontSeats() etc. Deseo instanciar como sigue¿Cómo diseño una subclase con características no disponibles en la clase base?

Vehicle convertible = new ConvertibleVehicle() 

tan Todavía tengo acceso a un método común como openDoor(), startEngine() etc. ¿Cómo diseño una solución de este tipo?

Para aclarar mis dos soluciones iniciales, ninguno de los cuales estoy feliz con son:

  1. tienen métodos ficticias foldRoof(), turboMode(), foldFrontSeats() que puedo reemplazar en ConvertibleVehicle solamente, dejando que se no hacer nada en otras subclases
  2. tener métodos abstractos foldRoof(), turboMode(), foldFrontSeats() y obligar a cada subclase para proporcionar una implementación, incluso si va a estar en blanco en todos los casos que no sean ConvertibleVehicle

lo anterior parece sl muy intrincados ya que ambos contaminan la clase base ya que agrego un número creciente de subclases cada uno con sus funciones únicas

Después de leer algunas de las respuestas quizás haya algún tipo de falla fundamental en mi diseño. Supongamos que tengo una clase que lleva VehicleFleet vehículos y les da instrucciones para conducir la siguiente manera:

public VehicleFleet(Vehicle[] myVehicles) { 

    for (int i=0; i < myVehicles.length; i++) { 
     myVehicles[i].drive(); 
    } 
} 

Supongamos que esto funciona para las subclases de vehículo docenas pero para ConvertibleVehicle también quiero plegar el techo antes de conducir. Para ello me subclase VehicleFleet de la siguiente manera:

public ConvertibleVehicleFleet(Vehicle[] myVehicles) { 

    for (int i=0; i < myVehicles.length; i++) { 
     myVehicles[i].foldRoof(); 
     myVehicles[i].drive(); 
    } 
} 

Esto me deja con una función foldRoof desordenado() atrapado en la clase base en la que no pertenece realmente que se anula sólo en el caso de ConvertibleVehicle y no hace nada en todos los otros casos. La solución funciona pero parece muy poco elegante. ¿Este problema se presta a una mejor arquitectura?

estoy usando Java aunque yo esperaría que una solución general se pudo encontrar que funcionará en cualquier lenguaje orientado a objetos y que no voy a necesitar confiar en el lenguaje peculiaridades específicas

+1

umm simplemente hereda Vehículo, agregando las funciones adicionales. –

+0

Creo que su pregunta ya describe el diseño. ¿Puedes aclarar tu pregunta? Algunas cosas que ayudarían sería si mencionas qué idioma estás usando y/o muestras de algún código. – allyourcode

Respuesta

3

He hecho esto en situaciones similares.

Opción A)

Si las operaciones especializadas son parte de la misma secuencia que una operación de base (por ejemplo ConvertibleVehicle necesita ser foldRoof antes de que pueda conducir) a continuación, sólo hay que poner la operación especializada dentro de la operación de base.

class Vehicle { 
    public abstract void drive(); 
} 

class ConvertibleVehicle { 
    public void drive() { 
     this.foldRoof(); 
     .... // drive 
    } 
    private void foldRoof() { 
     .... 
    } 
} 

Por lo tanto, el efecto de conducir una flota será que algunos de ellos se plieguen antes de conducir.

for(Vehicle v : vehicleFleet) { 
     v.drive(); 
} 

El método especializado no se expone en la interfaz pública del objeto sino que se invoca cuando es necesario.

Opción B)

Si la operación especializada no son parte de la misma secuencia y debe ser llamado bajo ciertas circunstancias "especiales" luego dejar una versión especializada de un cliente llama a esas operaciones especializadas. Advertencia, esto no es tan puro ni es de bajo acoplamiento, pero cuando ambos objetos (el cliente y el servicio) se crean con la misma "condición" o generador, entonces la mayoría de las veces está bien.

class Vehicle { 
    public void drive() { 
     .... 
    } 
} 
class ConvertibleVehicle extends Vehicle { 
     // specialized version may override base operation or may not. 
     public void drive() { 
      ... 
     } 

     public void foldRoof() { // specialized operation 
      ... 
     } 
} 

casi el mismo que el ejemplo anterior, sólo que en este caso es foldRoof público también.

La diferencia es que necesito un cliente especializado:

// Client (base handler) 
public class FleetHandler { 
    public void handle(Vehicle [] fleet) { 
      for(Vehicle v : fleet) { 
       v.drive(); 
      } 
    } 
} 

// Specialized client (sophisticate handler that is) 
public class RoofAwareFleetHandler extends FleetHandler { 
     public void handle(Vehicle [] fleet) { 
      for(Vehicle v : fleet) { 
       // there are two options. 
       // either all vehicles are ConvertibleVehicles (risky) then 
       ((ConvertibleVehicles)v).foldRoof(); 
       v.drive(); 

       // Or.. only some of them are (safer) . 
       if(v instenceOf ConvertibleVehicle) { 
        ((ConvertibleVehicles)v).foldRoof(); 
       } 
       v.drive(); 
      } 
     } 
    } 

Eso instaceof mirada un poco feo, pero que puede ser inline por vm moderna.

El punto aquí es que solo el cliente especializado sabe y puede invocar los métodos especializados. Es decir, sólo RoofAwareFleetHandler puede invocar foldRoof() en ** ** ConvertibleVehicle

El código final no cambia ...

public class Main { 
    public static void main(String [] args) { 
     FleetHandler fleetHandler = ..... 
     Vehicles [] fleet = .... 

      fleetHandler.handle(fleet); 
     } 
} 

Por supuesto, siempre me aseguro de la fleethandler y la gama de vehículos son compatibles (probablemente usando la fábrica de abstrac o el constructor)

Espero que esto ayude.

1

Esto es precisamente lo que hace la subclasificación : agrega funcionalidad no presente en una clase base.

class MyVehicle : public Vehicle { 

public: 
    void MyNewFunction() 
... 

hay dos (en realidad sólo) diferentes sabores de herencia: públicos y privados, lo que refleja el IS-A y relaciones Has-A, respectivamente. Con la herencia pública, estás agregando cosas directamente a una clase. Si tengo la clase Animal con los métodos Eat() y Walk(), puedo hacer una subclase llamada Cat que tiene el método Purr(). Un gato tiene métodos públicos Eat, Walk y Purr.

En el caso de una Pila basada en una Lista Vinculada, sin embargo, puedo decir que una Pila HAS-A LinkedList internamente. Como tal, no expongo públicamente ninguna característica de la clase base, la retengo como privada y tengo que ofrecer explícitamente lo que elijo como público. Una lista puede tener un método Insert(), pero para Stack, restrinjo la implementación y la vuelvo a colocar como Push(). No hay métodos públicos previos expuestos.

En C++, esto se define mediante el modificador de acceso dado antes de la clase base. Arriba, estoy usando herencia pública.Aquí, uso la herencia privada:

class MyVehicle : private Engine { 

Esto refleja que MyVehicle HAS-An Engine.

En última instancia, las subclases toman todo lo disponible en la clase base y le agrega cosas nuevas.

EDIT: Con esta nueva información, parece que realmente estás buscando, al parecer, es interfases según lo declarado por una anterior (votaron abajo) comentario. Este es uno de los grandes problemas con la herencia: la granularidad. Una de las grandes quejas de C++ es su implementación de herencia múltiple (una opción para lograr esto). ¿Puede indicar específicamente qué idioma está usando para que podamos asesorarlo adecuadamente?

+2

herencia pública NO es composición. La composición es ortogonal a la herencia. La composición usa una clase de implementación como miembro. –

+0

Vaya, mi error. Corregido – Anthony

3

Esta es una buena pregunta. Lo que implica es que tiene (o espera tener) un código que le pide a un vehículo que (por ejemplo) foldRoof(). Y eso es un problema, porque la mayoría de los vehículos no deben doblar sus techos. Solo el código que sabe que se trata de un ConvertibleVehicle debería llamar a ese método, lo que significa que es un método que debería estar solo en la clase ConvertibleVehicle. Es mejor de esta forma; tan pronto como intentes llamar a Vehicle.foldRoof(), tu editor te dirá que no se puede hacer. Lo que significa que necesita organizar su código para saber que está tratando con un vehículo convertible o cancelar la llamada a foldRoof().

5

Cualquier objeto que use el Vehículo no debería saber acerca de ConvertibleVehicle y sus métodos especializados. En un diseño orientado a objetos acoplado libremente, Driver solo sabría sobre la interfaz del vehículo. El controlador puede llamar a startEngine() en un Vehículo, pero depende de las subclases de Vehículo anular startEngine() para manejar implementaciones variables, como girar una tecla o presionar un botón.

Considerar la revisión de los siguientes dos enlaces que deberían ayudar a explicar este concepto: http://en.wikipedia.org/wiki/Liskov_substitution_principle http://en.wikipedia.org/wiki/Open/closed_principle

Considerar la publicación de un problema del mundo real que se siente conduce al dilema que usted describe aquí y alguien va a estar más que feliz demostrar un mejor enfoque.

+0

Buena respuesta. El interlocutor intenta violar los principios de OOD, que es un defecto de diseño, especialmente LSP. – Llyle

1

Creo que a la mayoría de la gente le falta el punto de la pregunta de Delta. Me parece que no está preguntando qué es la herencia. Él/Ella está preguntando acerca de las subclases que implementan la funcionalidad que no es una opción natural para una clase base, y el desorden resultante que puede surgir. Es decir. la introducción de métodos/funcionalidades específicos en la cadena jerárquica, o la exigencia de que las subclases implementen un contrato de funcionalidad que no es un ajuste natural.

También existe la cuestión de si es valioso poder tratar una clase base como la subclase en todos los casos (para evitar el lanzamiento y usarlos de manera intercambiable). * editar - esto se llama el principio de sustitución de Liskov (gracias por recordarme, Kyle).

0

¿Cómo el usuario de Vehicle sabe que es un ConvertibleVehicle? Ya sea que necesiten un lanzamiento dinámico para garantizar que sea correcto o que hayas proporcionado un método en el Vehículo para obtener los objetos de tipo real.

En el primer caso, el usuario ya tiene un ConvertibleVehicle como parte del lanzamiento dinámico. Solo pueden usar el nuevo puntero/referencia para acceder a los métodos de ConvertiblVehicle

En el segundo caso en el que el usuario verifica el tipo de objetos con uno de los métodos de Vehículos, puede simplemente echar el Vehículo a Vehículo Convertible y usarlo.

En general, el casting es una mala idea. Intenta hacer todo con el puntero de la clase base.El ejemplo de su automóvil no funciona bien porque los métodos son de un nivel demasiado bajo, crea funciones virtuales de nivel más alto.

Todo lo dicho. He necesitado todos los métodos de clases derivadas de la clase base. Podría haber lanzado a la clase derivada pero estaba involucrado en un marco y habría requerido mucho más esfuerzo. El viejo adagio "todos los problemas se pueden resolver con una capa más de indirección" es cómo resolví esto. Llamé a un método virtual en la clase base con el 'nombre' de la función que quería llamar. 'Nombre' puede ser una cadena o un número entero según sus necesidades. Es más lento, pero solo debe hacerlo con poca frecuencia, si su jerarquía de clases es lo suficientemente expresiva.

1

Para agregar a la excelente respuesta de Kyle W. Cartmell, y quizás para simplificar un poco la respuesta de Oscar Reyes ...

Es posible que desee considerar que la clase base defina un método llamado prepareToDrive() donde las clases heredadas podrían incluir las tareas de configuración que deben realizarse antes de la puesta en marcha. Llamar al drive() sería la manera de comenzar todo desde la perspectiva del usuario, por lo que tendríamos que refactorizar la unidad a una fase de "configuración" y una fase "avanzar".

public class Vehicle { 
    protected void prepareToDrive() { 
     // no-op in the base class 
    } 

    protected abstract void go(); 

    public final void drive() { 
     prepareToDrive(); 
     go(); 
    } 
} 

Ahora, subclases debe implementar el método protegido Go() (muy mal el nombre del método, pero se entiende la idea), que es donde hacen su manejo específico de clase de la conducción.

Ahora, su clase heredada podría tener este aspecto:

public class ConvertableVehicle extends Vehicle { 

    // override setup method 
    protected void prepareToDrive() { 
     foldRoof(); 
    } 

    protected void go() { 
     // however this works 
    } 

    protected void foldRoof() { 
     // ... whatever ... 
    } 
} 

Esta estructura también ayudaría cuando se ejecuta en TractorTrailerRig clase que necesita para asegurarse de que el remolque se carga y se fija antes de que pueda conducir correctamente.

0

Tener ConvertibleVehicle subclase Vehicle y agregar sus propios métodos como usted describe está perfectamente bien. Esa parte del diseño está bien. El problema que tienes es con la flota. ConvertibleFleet debería no ser una subclase de VehicleFleet. Un ejemplo te mostrará por qué. Digamos VehicleFleet es así:

public class VehicleFleet { 
    // other stuff... 

    public void add(Vehicle vehicle) { 
     // adds to collection... 
    } 
} 

Esto es perfectamente normal y sensible, se puede añadir cualquier Vehicle o subclase de ella a un VehicleFleet. Ahora, vamos a decir que también tenemos otro tipo de vehículo:

public class TruckVehicle extends Vehicle { 
    // truck-specific methods... 
} 

También podemos agregar esto a una VehicleFleet ya que es un vehículo. El problema es este: si ConvertibleFleet es una subclase de VehicleFleet, eso significa que también podemos agregar camiones a ConvertibleFleet. Eso está mal. Un ConvertibleFleet es no una subclase adecuada, ya que una operación que es válida para su padre (agregar un camión) no es válida para el niño.

La solución típica es utilizar un parámetro de tipo:

public class VehicleFleet<T extends Vehicle> { 
    void add(T vehicle) { 
     // add to collection... 
    } 
} 

Esto le permitirá definir las flotas específicas para ciertos tipos de vehículos. Tenga en cuenta que esto también significa que no existe una clase "base" VehicleFleet que pueda pasar a funciones que no les importa qué tipo de vehículo tiene la flota. Esto puede remediarse mediante otra capa de clase base (o interfaz):

public interface VehicleFleetBase { 
    Vehicle find(String name); 
    // note that 'add' methods or methods that pass in vehicles to the fleet 
    // should *not* be in here 
} 

public class VehicleFleet<T extends Vehicle> { 
    void add(T vehicle) { 
     // add to collection... 
    } 

    Vehicle find(String name) { 
     // look up vehicle... 
    } 
} 

Para los métodos que están tirando de los vehículos de flotas y no les importa de qué tipo son, puede pasar alrededor de VehicleFleetBase. Los métodos que necesitan insertar vehículos usan VehicleFleet<T> que está fuertemente tipado.

Cuestiones relacionadas