Pregunta

Digamos que tengo la siguiente estructura de clase:

class Car;
class FooCar : public Car;
class BarCar : public Car;

class Engine;
class FooEngine : public Engine;
class BarEngine : public Engine;

También demos un Car un identificador a su Engine . Se creará un FooCar con un FooEngine * y un BarCar se creará con un BarEngine * . ¿Hay alguna manera de organizar las cosas para que un objeto FooCar pueda llamar a las funciones miembro de FooEngine sin cambiar?

He aquí por qué la estructura de clases se presenta como está ahora:

  1. Todos los Car s tienen un Engine . Además, un FooCar solo usará un FooEngine .
  2. Hay datos y algoritmos compartidos por todos los Engine s que prefiero no copiar y pegar.
  3. Es posible que desee escribir una función que requiera un Engine para conocer su Car .

Tan pronto como escribí dynamic_cast al escribir este código, supe que probablemente estaba haciendo algo mal. ¿Hay una mejor manera de hacer esto?

ACTUALIZACIÓN:

Basado en las respuestas dadas hasta ahora, me inclino por dos posibilidades:

  1. Haga que Car proporcione una función virtual pura de getEngine () . Eso permitiría que FooCar y BarCar tengan implementaciones que devuelvan el tipo correcto de Engine .
  2. Absorba toda la funcionalidad Engine en el árbol de herencia Car . El Engine se desglosó por razones de mantenimiento (para mantener las cosas del Engine en un lugar separado). Es una compensación entre tener más clases pequeñas (pequeñas en líneas de código) versus tener menos clases grandes.

¿Existe una fuerte preferencia de la comunidad por una de estas soluciones? ¿Hay una tercera opción que no haya considerado?

¿Fue útil?

Solución

Supongo que Car tiene un puntero del motor, y es por eso que te encuentras abatido.

Saque el puntero de su clase base y reemplácelo con una función virtual pura get_engine (). Entonces su FooCar y BarCar pueden mantener punteros al tipo de motor correcto.

(Edit)

Por qué esto funciona:

Dado que la función virtual Car :: get_engine () devolvería una referencia o un puntero , C ++ permitirá que las clases derivadas implementen esta función con un diferente tipo de retorno , siempre que el tipo de retorno solo difiera por ser un tipo más derivado.

Esto se llama tipos de retorno covariantes , y permitirá cada Car escriba para devolver el Engine correcto.

Otros consejos

Solo una cosa que quería agregar: este diseño ya me huele mal por lo que llamo árboles paralelos .

Básicamente, si terminas con jerarquías de clases paralelas (como lo has hecho con Car y Engine), entonces solo estás buscando problemas.

Volvería a pensar si Engine (e incluso Car) necesita tener subclases o si todas son instancias diferentes de las mismas clases base respectivas.

También puede modelar el tipo de motor de la siguiente manera

template<class EngineType>
class Car
{
    protected:
        EngineType* getEngine() {return pEngine;}
    private:
        EngineType* pEngine;
};

class FooCar : public Car<FooEngine>

class BarCar : public Car<BarEngine>

No veo por qué un automóvil no puede estar compuesto por un motor (si BarCar siempre contendrá BarEngine). El motor tiene una relación bastante fuerte con el automóvil. Preferiría:

class BarCar:public Car
{
   //.....
   private:
     BarEngine engine;
}

¿Sería posible que FooCar use BarEngine?

Si no es así, es posible que desee utilizar un AbstractFactory para crear el objeto de automóvil correcto, con el motor correcto.

Puede almacenar FooEngine en FooCar, BarEngine en BarCar

class Car {
public:
  ...
  virtual Engine* getEngine() = 0;
  // maybe add const-variant
};

class FooCar : public Car
{
  FooEngine* engine;
public:
  FooCar(FooEngine* e) : engine(e) {}
  FooEngine* getEngine() { return engine; }
};

// BarCar similarly

El problema con este enfoque es que obtener un motor es una llamada virtual (si le preocupa eso), y un método para configurar un motor en Car requeriría downcasting.

Creo que depende de que Engine solo sea utilizado de forma privada por Car y sus hijos o si también desea usarlo en otros objetos.

Si las funcionalidades de Engine no son específicas de Car s, usaría un método virtual Engine * getEngine () en lugar de mantener un puntero en la clase base.

Si su lógica es específica de Car s, preferiría poner los datos / lógica comunes de Engine en un objeto separado (no necesariamente polimórfico) y mantener Implementación de FooEngine y BarEngine en su respectiva clase secundaria Car .

Cuando el reciclaje de la implementación es más necesario que la herencia de la interfaz, la composición de objetos a menudo ofrece una mayor flexibilidad.

El COM de Microsoft es un poco torpe, pero tiene un concepto novedoso: si tiene un puntero a una interfaz de un objeto, puede consultarlo para ver si es compatible con otras interfaces que utilicen función QueryInterface . La idea es dividir su clase Engine en múltiples interfaces para que cada una pueda usarse de forma independiente.

Permitiendo que no me haya perdido algo, esto debería ser bastante trivial.

La mejor manera de hacer esto es crear funciones virtuales puras en Engine, que luego se requieren en clases derivadas que se van a instanciar.

Una solución de crédito adicional probablemente sería tener una interfaz llamada IEngine, que en su lugar pasa a su automóvil, y cada función en IEngine es puramente virtual. Podría tener un 'BaseEngine' que implemente algunas de las funcionalidades que desee (es decir, 'compartidas') y luego eliminar las hojas.

La interfaz es agradable si alguna vez tiene algo que quiere 'verse' como un motor, pero probablemente no lo es (es decir, simulacros de clases de prueba y demás).

  

¿Hay alguna manera de organizar las cosas para que un objeto FooCar pueda llamar a las funciones miembro de FooEngine sin desanimar?

Me gusta esto:

class Car
{
  Engine* m_engine;
protected:
  Car(Engine* engine)
  : m_engine(engine)
  {}
};

class FooCar : public Car
{
  FooEngine* m_fooEngine;
public:
  FooCar(FooEngine* fooEngine)
  : base(fooEngine)
  , m_fooEngine(fooEngine)
  {}
};
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top