2008-11-21 12 views
11

Digamos que tenemos un concreto class Apple. (Se pueden crear instancias de los objetos de Apple.) Ahora, alguien viene y obtiene un resumen class Peach de Apple. Es abstracto porque introduce una nueva función virtual pura. El usuario de Peach ahora se ve obligado a derivar y definir esta nueva función. ¿Es este un patrón común? ¿Es correcto hacer esto?Derivando una clase abstracta de la clase concreta

muestra:


class Apple 
{ 
public: 
    virtual void MakePie(); 
    // more stuff here 
};

class Peach : public Apple { public: virtual void MakeDeliciousDesserts() = 0; // more stuff here };

Ahora digamos que tenemos un concreto class Berry. Alguien deriva un resumen class Tomato de Berry. Es abstracto porque sobrescribe una de las funciones virtuales de Berry y lo convierte en puramente virtual. El usuario de Tomato tiene que volver a implementar la función previamente definida en Berry. ¿Es este un patrón común? ¿Es correcto hacer esto?

Ejemplo:


class Berry 
{ 
public: 
    virtual void EatYummyPie(); 
    // more stuff here 
};

class Tomato : public Berry { public: virtual void EatYummyPie() = 0; // more stuff here };

Nota: Los nombres son artificiales y no reflejan ningún código real (con suerte). Ningún fruto ha sido dañado en la redacción de esta pregunta.

+0

¿Se puede editar su pregunta para mostrar algunas muestras de código, por favor? – quamrana

+1

@Marcin: "No se han dañado frutas en la redacción de esta pregunta" Eso hizo que mi día :) –

+0

Se agregaron fragmentos de código a petición. – Marcin

Respuesta

8

Re melocotón de Apple:

  • No practico si Apple es una clase de valor (es decir, tiene copia ctor, no idénticos casos puede ser igual, etc). Consulte Meyers Más eficaz Artículo 33 de C++ para saber por qué.
  • No hacerlo si Apple tiene un destructor pública no virtual, de lo contrario invitar a un comportamiento indefinido cuando los usuarios eliminan un Apple a través de un puntero a melocotón.
  • De lo contrario, probablemente esté seguro, porque no ha violado Liskov substitutability. A Peach IS-A Apple.
  • Si posee el código de Apple, prefiera factorizar una clase base abstracta común (Fruit quizás) y derivar Apple y Peach de ella.

Re tomate de Berry:

  • Igual que el anterior, más:
  • evitar, porque es inusual
  • Si debe, documentar lo que las clases derivadas de tomate debe hacer con el fin de no violar la capacidad de sustitución de Liskov. La función que está anulando en Berry - llamémosla Juice() - impone ciertos requisitos y hace ciertas promesas. Las implementaciones de clases derivadas de Juice() no requieren más y prometen nada menos. Entonces, un DerivedTomato IS-A Berry y un código que solo sabe sobre Berry están a salvo.

Posiblemente, cumplirá con el último requisito al documentar que DerivedTomatoes debe llamar al Berry::Juice(). Si es así, considere el uso de Template Method lugar:

class Tomato : public Berry 
{ 
public: 
    void Juice() 
    { 
     PrepareJuice(); 
     Berry::Juice(); 
    } 
    virtual void PrepareJuice() = 0; 
}; 

Ahora hay una excelente posibilidad de que un tomate es-A Berry, contrariamente a las expectativas botánicos.(La excepción es si las implementaciones de las clases derivadas de PrepareJuice imponen condiciones previas adicionales a las impuestas por Berry::Juice).

5

Me parece una indicación de un mal diseño. Podría ser forzado si quisieras tomar una definición concreta de una biblioteca cerrada y extenderla y ramificar un montón de cosas, pero en ese punto estaría considerando seriamente la directriz con respecto a la encapsulación sobre herencia. Si puedes encapsular , probablemente deberías.

Sí, cuanto más lo pienso, esta es una muy mala idea.

2

No necesariamente mal, pero definitivamente huele mal. Especialmente si dejas la fruta al sol por mucho tiempo. (Y no creo que a mi dentista le gustaría comer manzanas de hormigón.)

Aunque, lo principal que veo aquí que huele mal no es tanto la clase abstracta derivada de una clase concreta, sino la herencia REALMENTE PROFUNDA jerarquía.

EDIT: relectura Veo que estas son dos jerarquías. Todas las cosas de fruta me confundieron.

0

Hmmm ... pensando "qué ..." durante un par de segundos, llegué a la conclusión de que no es común ... Además, no derivaría melocotón de manzana y tomate de Berry ... ¿tiene alguna mejor ejemplo? :)

es un montón de cosas raras que puede hacer en C++ ... ni siquiera puedo pensar en un 1% de la misma ...

Sobre anular un virtual con un virtual puro, probablemente solo lo ocultes y será realmente extraño ...

Si puedes encontrar un estúpido compilador de C++ que vincule esta función como virtual, obtendrás tiempo de ejecución llamada de función virtual pura ...

creo que esto sólo se puede hacer por un corte y no tengo idea de qué tipo de truco realmente ...

+0

"_Acerca de anular una virtual con una virtual pura, probablemente puedes simplemente ocultarla_" ¿por qué una función con la misma firma ** no ** anula una función virtual de la clase base? – curiousguy

0

para responder a su primera pregunta, puede hacerlo desde los usuarios de Apple, si se les da una instancia concreta derivada de Peach, no sabrán nada diferente. Y la instancia no sabrá que no es una Apple (a menos que haya algunas funciones virtuales de Apple anuladas de las que no nos informó).

Todavía no me puedo imaginar lo útil que sería anular una función virtual con una virtual pura, ¿eso es legal?

En general, usted desea conformarse con Scott Meyers "Hacer que todas las clases de hojas no sean abstractas" de sus libros.

De todos modos, aparte de eso, lo que describes parece ser legal, es solo que no puedo ver que lo necesites con tanta frecuencia.

+0

"_No puedo imaginar aún cuán útil sería anular una función virtual con una función virtual pura. ¿Eso es legal? _" Sí, ¿por qué no sería así? – curiousguy

2

Si utiliza la práctica recomendada de tener el modelo de herencia "is-a", este patrón casi nunca aparecerá.

Una vez que tiene una clase concreta, está diciendo que es algo que realmente puede crear una instancia de. Si luego deriva una clase abstracta de él, entonces algo que es un atributo de la clase base no es verdadero de la clase derivada, que debería establecer klaxons de que algo no está bien.

En cuanto a su ejemplo, un melocotón no es una manzana, por lo que no debe derivarse de ella. Lo mismo es cierto para Tomate derivado de Berry.

Aquí es donde normalmente aconsejo la contención, pero eso ni siquiera parece ser un buen modelo, ya que una manzana no contiene un melocotón.

En este caso, tendría en cuenta la interfaz común: PieFilling o DessertItem.

+0

"_tributo de la clase base_" No estoy seguro de lo que usted llama un "atributo de la clase base". – curiousguy

+0

"atributo" fue probablemente una elección de palabra pobre, ya que también se refiere a un concepto de programación. – JohnMcG

+0

Lo que quise decir es "rasgo" o "algo que es cierto para". es decir, "es cierto que puede crear una instancia de la clase base, pero no es cierto que pueda crear una instancia de la clase derivada, por lo tanto, deriva que no es el caso derivado de la base" is-a ". – JohnMcG

1

un poco inusual, pero si tenía alguna otra subclase de la clase base y las subclases de la clase abstracta tenido suficiente material común para justificar la existencia de la clase abstracta como:

class Concrete 
{ 
public: 
    virtual void eat() {} 
}; 
class Sub::public Concrete { // some concrete subclass 
    virtual void eat() {} 
}; 
class Abstract:public Concrete // abstract subclass 
{ 
public: 
    virtual void eat()=0; 
    // and some stuff common to Sub1 and Sub2 
}; 
class Sub1:public Abstract { 
    void eat() {} 
}; 
class Sub2:public Abstract { 
    void eat() {} 
}; 
int main() { 
    Concrete *sub1=new Sub1(),*sub2=new Sub2(); 
    sub1->eat(); 
    sub2->eat(); 
    return 0; 
} 
Cuestiones relacionadas