2008-11-21 5 views
11

Disons que nous avons un class Apple concret. (Les objets Apple peuvent être instanciés.) Maintenant, quelqu'un vient et tire un résumé class Peach d'Apple. C'est abstrait car il introduit une nouvelle fonction virtuelle pure. L'utilisateur de Peach est maintenant contraint d'en dériver et de définir cette nouvelle fonction. Est-ce un modèle commun? Est-ce correct à faire?Dérivation d'une classe abstraite de la classe concrète

Exemple:


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

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

Maintenant, disons que nous avons un béton class Berry. Quelqu'un tire un résumé class Tomato de Berry. C'est abstrait car il écrase l'une des fonctions virtuelles de Berry, et le rend virtuel pur. L'utilisateur de Tomato doit ré-implémenter la fonction précédemment définie dans Berry. Est-ce un modèle commun? Est-ce correct à faire?

Exemple:


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

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

Remarque: Les noms sont artificiels et ne reflètent aucun code réel (espérons-le). Aucun fruit n'a été blessé dans l'écriture de cette question.

+0

Pouvez-vous modifier votre question pour montrer quelques exemples de code s'il vous plaît? – quamrana

+1

@ Marcin: "Aucun fruit n'a été lésé dans l'écriture de cette question" Cela a fait ma journée :) –

+0

Des extraits de code ajoutés sur demande. – Marcin

Répondre

8

Re Peach d'Apple:

  • Ne pas le faire si Apple est une classe de valeur (c.-à-copie a cteur, les instances non identiques peut être égal, etc.). Voir Meyers Plus efficace Item C++ 33 pour pourquoi.
  • Ne pas le faire si Apple a une publique nonvirtual destructor, sinon vous invitez un comportement non défini lorsque vos utilisateurs supprimer un Apple via un pointeur à Peach. Sinon, vous êtes probablement en sécurité, car vous n'avez pas violé Liskov substitutability. Un Peach IS-A Apple.
  • Si vous possédez le code Apple, préférez prendre en compte une classe de base abstraite commune (Fruit peut-être) et en tirer Apple et Peach.

Re Tomate du Berry:

  • comme ci-dessus, plus:
  • Évitez, car il est inhabituel
  • Si vous devez, documenter les classes dérivées de la tomate doit faire pour ne pas violer la substituabilité de Liskov. La fonction que vous remplacez dans Berry - appelons-le Juice() - impose certaines exigences et fait certaines promesses. Les implémentations de classes dérivées de Juice() ne doivent pas exiger plus et promettent pas moins. Puis un DerivedTomato IS-A Berry et un code qui ne connaît que Berry est sûr.

Eventuellement, vous rencontrerez la dernière exigence en documentant que DerivedTomatoes doit appeler Berry::Juice(). Si oui, envisagez d'utiliser la méthode modèle à la place:

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

Maintenant, il y a une excellente chance qu'une tomate IS-A Berry, contrairement aux attentes botaniques.(L'exception est si les implémentations des classes dérivées de PrepareJuice imposent des conditions préalables supplémentaires au-delà de celles imposées par Berry::Juice).

5

Cela me semble être une indication d'un mauvais design. Pourrait être forcé si vous vouliez prendre une définition concrète à partir d'une bibliothèque fermée et l'étendre et ramasser un tas de choses, mais à ce moment-là, je serais sérieusement considérer la directive concernant l'encapsulation sur l'héritage .. Si vous pouvez encapsuler , vous devriez probablement.

Oui, plus j'y pense, c'est une très mauvaise idée.

2

Pas nécessairement mauvais, mais certainement malodorant. Surtout si vous laissez le fruit au soleil trop longtemps. (Et je ne pense pas que mon dentiste voudrait que je mange des pommes concrètes.)

Cependant, la chose principale que je vois ici qui est malodorante n'est pas tant la classe abstraite dérivée d'une classe concrète, mais l'héritage VRAIMENT PROFOND hiérarchie.

EDIT: relecture Je vois qu'il s'agit de deux hiérarchies. Tous les trucs de fruits m'ont mélangé.

0

Hmmm ... en pensant "quoi a ....." pendant quelques secondes, j'en arrive à la conclusion qu'il n'est pas commun ... Aussi, je ne dériverais pas Peach d'Apple et de Tomate de Berry ... avez-vous un meilleur exemple? :)

il y a beaucoup de merde bizarre que vous pouvez faire en C++ ... Je ne peux même pas penser de 1% de celui-ci ...

a propos de Remplacer un virtuel avec un virtuel pur, vous pouvez probablement le cacher et il sera vraiment bizarre ...

Si vous pouvez trouver un compilateur C++ stupide qui lierait cette fonction comme un virtuel, alors vous obtiendrez la durée d'exécution pur appel de fonction virtuelle ...

Je pense que cela ne peut être fait pour un hack et je ne sais pas quel genre de bidouille vraiment ...

+0

"_A propos du remplacement d'un virtuel par un virtuel pur, vous pouvez probablement le cacher_" pourquoi une fonction avec la même signature ** pas ** remplacerait une fonction virtuelle de classe de base? – curiousguy

0

Pour répondre à votre première question, vous pouvez le faire depuis Les utilisateurs d'Apple, si on leur donne une instance concrète dérivée de Peach, n'en connaîtront pas d'autres. Et l'instance ne saura pas que ce n'est pas un Apple (à moins qu'il y ait des fonctions virtuelles d'Apple qui sont remplacées et dont vous ne nous avez pas parlé).

Je ne peux pas encore imaginer à quel point il serait utile de remplacer une fonction virtuelle par une fonction virtuelle pure - est-ce encore légal?

En général, vous voulez vous conformer à l'article de Scott Meyers "Faire toutes les classes abstraites" de ses livres. Quoi qu'il en soit, en dehors de ce que vous décrivez semble être légal - c'est juste que je ne peux pas vous voir en avoir besoin si souvent.

+0

"_Je ne peux pas encore imaginer à quel point il serait utile de remplacer une fonction virtuelle par une fonction virtuelle pure - est-ce encore légal? _" Oui, pourquoi ça ne le serait pas? – curiousguy

2

Si vous utilisez la méthode recommandée pour le modèle d'héritage "is-a", ce modèle ne se présentera pratiquement jamais.

Une fois que vous avez une classe concrète, vous dites que c'est quelque chose dont vous pouvez réellement créer une instance. Si vous en dérivez ensuite une classe abstraite, alors quelque chose qui est un attribut de la classe de base n'est pas vrai de la classe dérivée, ce qui devrait faire de klaxons que quelque chose ne va pas.

En regardant votre exemple, une pêche n'est pas une pomme, elle ne devrait donc pas en être dérivée. La même chose est vraie pour Tomato dérivant de Berry.

C'est là que je conseillerais généralement confinement, mais cela ne semble même pas être un bon modèle, car un Apple ne contient pas de pêche.

Dans ce cas, je voudrais factoriser l'interface commune - PieFilling ou DessertItem.

+0

"_attribut de la classe de base_" Je ne suis pas sûr de ce que vous appelez un "attribut de la classe de base". – curiousguy

+0

"attribut" était probablement un mauvais choix de mot, puisqu'il fait également référence à un concept de programmation. – JohnMcG

+0

Ce que je voulais dire était "trait" ou "quelque chose qui est vrai pour". c'est-à-dire "Il est vrai que vous pouvez instancier la classe de base, mais il n'est pas vrai que vous pouvez instancier la classe dérivée, donc dérivée ce n'est pas le cas que la base dérivée est" -a " – JohnMcG

1

un peu inhabituel, mais si vous aviez une autre sous-classe de la classe de base et les sous-classes de la classe abstraite avait suffisamment de choses communes pour justifier l'existence de la classe abstraite comme:

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; 
} 
Questions connexes