c++ - implementación - ejercicios clases abstractas
Derivando una clase abstracta de una clase concreta (7)
Digamos que tenemos una class Apple
concreta class Apple
. (Los objetos de Apple se pueden crear instancias.) Ahora, alguien viene y deriva una class Peach
abstracta 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?
Muestra:
class Apple
{
public:
virtual void MakePie();
// more stuff here
};
class Peach : public Apple
{
public:
virtual void MakeDeliciousDesserts() = 0;
// more stuff here
};
class Berry
concreta class Berry
. Alguien deriva una class Tomato
abstracta 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? Muestra:
class Berry
{
public:
virtual void EatYummyPie();
// more stuff here
};
class Tomato : public Berry
{
public:
virtual void EatYummyPie() = 0;
// more stuff here
};
Hmmm ... al pensar "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 ... ¿Tienes algún mejor ejemplo? :)
Es un montón de cosas raras que puedes hacer en C ++ ... Ni siquiera puedo pensar en el 1% ...
Sobre anular una virtual con una virtual pura, probablemente puedas simplemente ocultarla y será realmente extraño ...
Si puede encontrar un estúpido compilador de C ++ que vincule esta función como virtual, obtendrá una llamada de función virtual pura en tiempo de ejecución ...
Creo que esto solo se puede hacer por un truco y no tengo idea de qué clase de truco realmente ...
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.
No necesariamente está 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).
Sin embargo, lo principal que veo aquí que huele mal no es tanto la clase abstracta derivada de una clase concreta, sino la jerarquía de herencia REALMENTE PROFUNDA.
EDITAR: relectura veo que estas son dos jerarquías. Todas las cosas de fruta me confundieron.
Para responder a su primera pregunta, puede hacerlo ya que los usuarios de Apple, si se les proporciona una instancia concreta derivada de Peach, no conocerá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 función virtual pura, ¿eso es legal?
En general, usted desea conformarse con el ítem "Hacer todas las clases no hojas abstractas" de Scott Meyers de sus libros.
De todos modos, aparte de eso, lo que describes parece ser legal, es solo que no puedo verte tan necesitado.
Re Peach de Apple:
- No lo haga si Apple es una clase de valor (es decir, tiene copy ctor, las instancias no idénticas pueden ser iguales, etc.). Ver el artículo 33 de Meyers más efectivo de C ++ para saber por qué.
- No lo haga si Apple tiene un destructor público no virtual, de lo contrario, invitará a un comportamiento indefinido cuando los usuarios eliminen un Apple mediante un puntero a Peach.
- De lo contrario, probablemente estés a salvo, porque no has violado la capacidad de sustitución de Liskov . 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 arriba, más:
- Evitar, porque es inusual
- Si debe hacerlo, documente qué clases derivadas de Tomate deben hacer para 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 las clases derivadas deJuice()
no requieren más y no prometen menos. Entonces, un DerivedTomato IS-A Berry y un código que solo sabe sobre Berry están a salvo.
Posiblemente, cumplirá el último requisito al documentar que DerivedTomatoes debe llamar a Berry::Juice()
. Si es así, considere usar el Método de plantilla en su lugar:
class Tomato : public Berry
{
public:
void Juice()
{
PrepareJuice();
Berry::Juice();
}
virtual void PrepareJuice() = 0;
};
Ahora hay una excelente posibilidad de que un tomate IS-A Berry, contrario a las expectativas botánicas. (La excepción es si las implementaciones de las clases derivadas de PrepareJuice
imponen condiciones previas adicionales a las impuestas por Berry::Juice
).
Si usas la práctica recomendada de tener el modelo de herencia "is-a", este patrón casi nunca aparecerá.
Una vez que tienes una clase concreta, estás diciendo que es algo de lo que puedes crear una instancia. 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 él. Lo mismo es cierto para Tomate derivado de Berry.
Aquí es donde generalmente recomendaría 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.
un poco inusual, pero si tuviera alguna otra subclase de la clase base y las subclases de la clase abstracta tuviera suficientes elementos comunes 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;
}