2010-11-12 11 views
5

Estoy tratando de utilizar la clase base abstracta C++ de la misma manera que con la interfaz Java. Se supone que hemos siguientes clases de interfaz con funciones virtuales solamente puros:Herencia paralela entre clases de interfaz y clases de implementación en C++

class Shape { virtual double area()=0; }; 
class Square : public Shape { virtual void setLength(double length)=0; }; 
class Rectangle : public Square { virtual void setWidth(double width)=0; }; 

y yo tratamos de poner en práctica cuadrado y del rectángulo de la siguiente manera:

class SquareImpl : public Square { /*implementation*/ }; 
class RectangleImpl : public SquareImpl, Rectangle { /*implementation*/ }; 

Dónde RectangleImpl hereda tanto SquareImpl y Rectangle volver a utilizar, por ejemplo , SquareImpl::area(). Sin embargo, cuando intento compilar, surgen dos problemas: en primer lugar, todos los métodos en SquareImpl no se heredan correctamente y tengo que volver a implementar manualmente RectangleImpl::area() y RectangleImpl::setLength(). En segundo lugar, esto todavía presenta el problema del diamante que Shape es una base ambigua de RectangleImpl.

Pude compilar el código si heredo virtualmente Square de Shape, pero no creo que el rendimiento se escale con más interfaces derivadas agregadas. También extrañamente, RectangleImpl aún no hereda SquareImpl::setLength() aunque SquareImpl::area() se hereda bien. (ignore la practicidad aquí)

Otra solución podría ser hacer que las interfaces sean independientes entre sí, es decir, hacer que Square no se herede de Shape. Pero hacerlo me hará perder el acceso a los métodos en Shape si defino funciones que toman un puntero Square*. También hará que static_cast sea imposible entre Shape y Square.

Así que mi pregunta es, ¿hay algún otro patrón de diseño en C++ para resolver este tipo de herencia paralela entre las clases de interfaz y las clases de implementación, sin requerir herencia virtual?

(edición aclaración: el código de ejemplo anterior son sólo mi ilustración ficticia sobre la herencia paralelo entre las interfaces e implementaciones entiendo que hay mejores formas de implementar formas pero mi problema no es sobre cómo implementar formas..)

+0

"¿Dónde RectangleImpl hereda tanto SquareImpl y Rectángulo para la reutilización, por ejemplo, SquareImpl :: área()" => heredan para ser reutilizado, no volver a utilizar. – icecrime

+0

víctima de http://stackoverflow.com/questions/249500/looking-for-a-better-way-than-virtual-inheritance-in-c? –

+0

¿Está prevista la herencia no pública de Rectangle en RectangleImpl? – daramarak

Respuesta

3

Lo que tiene aquí es el caso del Diamond Problem, que puede ocurrir en cualquier lenguaje OO que permita herencia múltiple. Esto, por cierto, es una de las razones por las cuales los diseñadores de Java decidieron no tener herencia múltiple, y se les ocurrió la idea de una interfaz.

La forma en que C++ se ocupa del problema de los diamantes es Virtual Inheritance.

Y, como señaló codymanix, el cuadrado y el rectángulo es un ejemplo notoriamente malo para el diseño orientado a objetos, porque en lo que concierne a OO a square is not a rectangle.

Pareja más puntos. Primero, el término para lo que está haciendo aquí es herencia múltiple, no "herencia paralela". En segundo lugar, en este caso particular, realmente tiene poco sentido tener un class Square y un class SquareImpl. Si cree que puede tener implementaciones diferentes de Square, solo debe tener una clase base que proporcione una implementación predeterminada y funciones virtuales que puedan ser reemplazadas por una clase derivada si es necesario. En otras palabras, debe lanzar Square y SquareImpl en una clase con funciones virtuales.

Ciertamente puede utilizar una clase abstracta de C++ como una interfaz de Java, pero la mayoría de las veces no hay ninguna razón para ello. Las interfaces se agregaron a Java precisamente como una forma de evitar la herencia múltiple. En C++ puedes seguir adelante y usar herencia múltiple, aunque siempre debes hacerlo con mucha prudencia.

+1

Utilicé las formas como ejemplo de implementación de algo que es potencialmente complejo detrás de una clase de interfaz C++. Si bien en este caso 'Square' es lo suficientemente simple como para tener su implementación e interfaz combinadas, ese podría no ser el caso para otras situaciones. De todos modos, gracias por la nota de que Shape es un ejemplo notoriamente malo. Supongo que usaré FooBar en mis ejemplos la próxima vez. – Soares

+0

Su sugerencia de convertir Square y SquareImpl en una sola clase funciona en este caso, pero el problema de Soares se produce con frecuencia al escribir código para la inyección de dependencia. Para DI, siempre tendrás 2 clases de SquareImpl que tienen implementaciones muy diferentes (una es real, otra es un objeto simulado simple). No hay código para compartir, por lo que tiene mucho sentido hacer que hereden de la misma interfaz. Para tu información, la solución que utilicé para DI es hacer herencia virtual de la pila de herencia de la interfaz. Una pequeña duplicación, pero al menos funciona: / – Weston

0

El cuadrado no es rectángulo, y el rectángulo no es cuadrado. Lo único que tienen en común es que son Shapes. Por lo tanto:

class Square : public Shape {...}; 
class Rectangle : public Shape {...}; 

Sus funciones de inicialización son diferentes, y Square::setSide(double)Rectangle::setLengthAndWidth(double, double). No necesita clases * Impl. Haz tus cosas en Cuadrado y Rectángulo.

+0

Mi ejemplo es solo una ilustración del problema. Entiendo que hay mejores formas de implementar la forma, pero solo hago las clases para mostrar mi punto de herencia paralela. – Soares

+1

Ah, mala ilustración. Hmm, creo que deberías eliminar 'public SquareImpl' de' clase RectangleImpl', y poner el código común en algunas funciones estáticas que pueden llamar tanto 'SquareImpl' como' RectangleImpl'. – Dialecticus

+1

-1 Siento que realmente no respondiste al problema de los que preguntan, creo que está claro que él tiene lo básico aquí – Elemental

0

Creo que debería buscar la herencia virtual aquí para que solo tenga una sola instancia de forma debajo de todo esto, esto parece obvio desde un punto de vista semántico, es decir, la forma que square y rectangleimpl es claramente la misma forma .

No estoy seguro del problema de rendimiento que mencionas; eso no me parece un problema (en el sentido de que no hay una sobrecarga adicional aparte de llamar a cualquier función v). No veo por qué no puede acceder al setLength desde el cuadrado, es difícil ver por qué podría estar experimentando esto y no tiene el origen para las implementaciones.

0

Su problema es Rectangle-> Square-> Shape no sabe nada acerca de SquareImpl por lo que no puede usar estas funciones para satisfacer los requisitos de su función abstracta.

La manera más fácil es no mezclar la interfaz y la herencia de implementación cuando están estrechamente vinculados de esta manera. Haga que RectangleImpl herede de las interfaces Square y Rectangle. Si SquareImpl y RectangleImpl están replicando demasiado el código de cada uno, use una clase que haga todo este trabajo y que tenga como función de miembro en cada implementación.

0

Después de reconsiderar por una noche y refiriéndome a la solución sean proporcionada en Looking for a better way than virtual inheritance in C++, obtuve la siguiente solución.

Aquí redefino el problema para que sea más abstracto y así evitar la confusión que teníamos en las formas. Tenemos una interfaz Ball que puede rodar, una interfaz FooBall que contiene métodos específicos de Foo, y una interfaz FooBarBall que también es FooBall y contiene métodos específicos de Foo y específicos de Bar. Al igual que el problema original, tenemos una implementación de FooBall y deseamos derivarla para que cubra los métodos específicos de Bar también. pero heredar tanto la interfaz como la implementación introducirá la herencia del diamante.

Para resolver el problema, en vez de poner directamente Foo y la barra de métodos específicos en los Ball interfaces derivadas, puse un único método en un FooBall interfaz derivada que convierte el objeto en un objeto Foo a través del método toFoo(). De esta forma, las implementaciones pueden mezclarse en la interfaz independiente Foo y Bar sin introducir la herencia del diamante.

Aún así, no todos los códigos se pueden eliminar para derivar libremente todas las barras de Foos. Todavía tenemos que escribir implementaciones independientes de Ball, FooBall y FooBarBall que no heredan entre sí.Pero podemos usar el patrón compuesto para envolver los objetos reales Foo y Bar que se implementan de manera diferente. De esta forma, aún podemos eliminar bastante código si tenemos muchas implementaciones de Foo y Bar.

#include <stdio.h> 

class Ball { 
    public: 
    // All balls can roll. 
    virtual void roll() = 0; 

    // Ball has many other methods that are not 
    // covered here. 

    virtual inline ~Ball() { 
     printf("deleting Ball\n"); 
    }; 
}; 

class Foo { 
    public: 
    virtual void doFoo() = 0; 

    // do some very complicated stuff. 
    virtual void complexFoo() = 0; 

    virtual inline ~Foo() {}; 
}; 

/** 
* We assume that classes that implement Bar also 
* implement the Foo interface. The Bar interface 
* specification failed to enforce this constraint 
* by inheriting from Foo because it will introduce 
* diamond inheritance into the implementation. 
**/ 
class Bar { 
    public: 
    virtual void doBar() = 0; 
    virtual void complicatedBar() = 0; 

    virtual inline ~Bar() {}; 
}; 

class FooBall : public Ball { 
    public: 
    virtual Foo* toFoo() = 0; 

    virtual inline ~FooBall() {}; 
}; 

/** 
* A BarBall is always also a FooBall and support 
* both Foo and Bar methods. 
**/ 
class FooBarBall : public FooBall { 
    public: 
    virtual Bar* toBar() = 0; 

    virtual inline ~FooBarBall() {}; 
}; 


/* Composite Implementation */ 

class FooImpl_A : public Foo { 
    public: 
    virtual void doFoo() { 
     printf("FooImpl_A::doFoo()\n"); 
    }; 

    virtual void complexFoo() { 
     printf("FooImpl_A::complexFoo()\n"); 
    } 

    virtual inline ~FooImpl_A() { 
     printf("deleting FooImpl_A\n"); 
    } 
}; 

class FooBarImpl_A : public FooImpl_A, public Bar { 
    public: 
    virtual void doBar() { 
     printf("BarImpl_A::doBar()\n"); 
    } 

    virtual void complicatedBar() {; 
     printf("BarImpl_A::complicatedBar()\n"); 
    } 

    virtual inline ~FooBarImpl_A() { 
     printf("deleting FooBarImpl_A\n"); 
    } 
}; 

/* Composite Pattern */ 
class FooBarBallContainer : public FooBarBall { 
    public: 

    /* FooBarBallImpl_A can take any class that 
    * implements both the Foo and Bar interface, 
    * including classes that inherit FooBarImpl_A 
    * and other different implementations. 
    * 
    * We'll assume that realFoo and realBar are 
    * actually the same object as Foo methods have 
    * side effect on Bar methods. If they are not 
    * the same object, a third argument with false 
    * value need to be supplied. 
    */ 
    FooBarBallContainer(Foo* realFoo, Bar* realBar, bool sameObject=true) : 
    _realFoo(realFoo), _realBar(realBar), _sameObject(sameObject) {} 

    virtual void roll() { 
     // roll makes use of FooBar methods 
     _realBar->doBar(); 
     _realFoo->complexFoo(); 
    } 

    virtual Foo* toFoo() { 
     return _realFoo; 
    } 

    virtual Bar* toBar() { 
     return _realBar; 
    } 

    virtual ~FooBarBallContainer() { 
     delete _realFoo; 

     // Check if realFoo and realBar are 
     // not the same object to avoid deleting 
     // it twice. 
     if(!_sameObject) { 
      delete _realBar; 
     } 
    } 

    private: 
    Foo* _realFoo; 
    Bar* _realBar; 
    bool _sameObject; 
}; 


/* Monolithic Implmentation */ 

class FooBarBallImpl_B : public FooBarBall, 
    public Foo, public Bar { 

    public: 
    virtual void roll() { 
     complicatedBar(); 
     doFoo(); 
    } 

    virtual Foo* toFoo() { 
     return (Foo*) this; 
    } 

    virtual Bar* toBar() { 
     return (Bar*) this; 
    } 

    virtual void doFoo() { 
     printf("FooBarBallImpl_B::doFoo()\n"); 
    } 

    virtual void complexFoo() { 
     printf("FooBarBallImpl_B::complexFoo()\n"); 
    } 

    virtual void doBar() { 
     printf("FooBarBallImpl_B::doBar()\n"); 
    } 

    virtual void complicatedBar() { 
     printf("FooBarBallImpl_B::complicatedBar()\n"); 
    } 

}; 

/* Example usage of FooBarBall */ 
void processFooBarBall(FooBarBall *ball) { 

    Foo *foo = ball->toFoo(); 
    foo->doFoo(); 

    ball->roll(); 

    Bar *bar = ball->toBar(); 
    bar->complicatedBar(); 
} 

main() { 

    FooBarImpl_A *fooBar = new FooBarImpl_A(); 
    FooBarBall *container = new FooBarBallContainer(fooBar, fooBar); 

    printf 
    processFooBarBall(container); 
    delete container; 

    FooBarBallImpl_B *ball = new FooBarBallImpl_B(); 
    processFooBarBall(ball); 

    // we can even wrap FooBarBallImpl_B into the container 
    // but the behavior of roll() will become different 
    container = new FooBarBallContainer(ball, ball); 
    processFooBarBall(container); 

    delete container; 

} 
Cuestiones relacionadas