2009-01-11 15 views
2

Dans une simulation de physique C++, j'ai une classe appelée Circle et Square. Ce sont des formes, et ont une méthode appelée push(), qui lui applique une force. Il y a alors un cas particulier de Circle, appelé SpecialCircle, dans lequel push() devrait présenter des propriétés légèrement différentes. Mais en fait, il y a aussi SpecialSquare() qui devrait présenter les mêmes propriétés de force. Je voudrais donc avoir une classe de base abstraite appelée Shape qui s'occupe des cercles et des carrés, mais j'aimerais aussi une classe de base abstraite appelée Special, qui applique des propriétés spéciales à force().Héritage multiple en C++ entraînant des difficultés à surcharger les fonctionnalités communes

Quelle est la meilleure façon de concevoir cette structure de classe?

Jusqu'à présent, j'ai:

class Shape { 
    virtual void push(); 
}; 

class Circle : public Shape {}; 

class Square : public Shape {}; 

class Special { 
    virtual void push(); 
}; 

class SpecialCircle : public Circle, Special {}; 

class SpecialSquare : public Square, Special {}; 

Bien sûr, la compilation ne sera pas ci-dessus, puisque spécial :: push() et de forme :: conflit push(). Je reçois "erreur: demande de membre" push "est ambigu", comme prévu.

Comment est-ce que je peux réorganiser ma structure de classe pour que Circle et Square puissent partager certaines propriétés entre elles, mais SpecialCircle et SpecialSquare peuvent toujours hériter de Shape, et héritent également des fonctionnalités modifiées de Special?

Merci. Ps., Est-ce le problème de l'héritage du diamant?

+1

Ce n'est pas le problème du diamant - c'est quand vous héritez de 2 classes qui héritent chacune d'une même classe. Donc la classe dérivée a 2 chemins vers la classe de base ultime. –

+0

"class Square: public Square {};" Faute de frappe? – dmckee

Répondre

8

Une autre solution (il peut ou ne peut pas répondre à vos besoins, cela dépend des détails de l'implémentation):

  • ont le comportement de la classe, et laissez-NormalBehavior et SpecialBehavior en héritent.
  • Avoir la classe Shape, et laisser Square et Circle en hériter. Let Shape est un type agrégé, avec un membre Behavior (c'est-à-dire que vous transmettez un objet Behavior aux différents constructeurs Shape). En d'autres termes, laissez une forme avoir un comportement.
  • Déléguez les différences réelles dans le comportement des formes aux méthodes de la hiérarchie Behavior.

A l'inverse, vous pouvez:

  • Avoir la PhysicalObject de classe, et de laisser NormalObject et SpecialObject en héritent; Ayez la classe Shape, et laissez Square et Circle en hériter;
  • Laissez un objet PhysicalObject avoir une forme.

Préférer l'agrégation sur l'héritage. Ceci est une application du Bridge pattern. L'avantage de cette stratégie par rapport à Square, SpecialSquare, Circle et SpecialCircle, c'est que demain vous devrez ajouter Rectangle, Hexagon et ainsi de suite, et pour chaque forme que vous ajoutez, vous devrez implémenter deux classes (le code dupliqué est le mal); C'est, à mon avis, le vrai problème que Bridge aborde. Avoir une forme spéciale de la forme et SpecialCircle et SpecialSquare de SpecialShape

3

On dit que tout problème dans le logiciel peut être résolu en ajoutant une couche supplémentaire d'indirection.

Herb Sutter a un excellent article sur la façon de résoudre votre problème: Multiple Inheritance - Part III

En bref, vous utilisez des classes intermédiaires pour « renommer » les fonctions virtuelles. Comme Herb dit:

Renaming Virtual Functions

If the two inherited functions had different signatures, there would be no problem: We would just override them independently as usual. The trick, then, is to somehow change the signature of at least one of the two inherited functions.

The way to change a base class function's signature is to create an intermediate class which derives from the base class, declares a new virtual function, and overrides the inherited version to call the new function

Voici un exemple à long en utilisant vos classes:

class Shape { 
public: 
    virtual void push() = 0; 
}; 

class Circle : public Shape 
{ 
public: 
    void push() { 
     printf("Circle::push()\n"); 
    } 
}; 

class Square : public Shape 
{ 
public: 
    void push() { 
     printf("Square::push()\n"); 
    } 
}; 

class Special { 
public: 
    virtual void push() = 0; 
}; 


class Circle2: public Circle 
{ 
public: 
    virtual void pushCircle() = 0; 
    void push() { 
     pushCircle(); 
    } 
}; 

class Square2: public Square 
{ 
public: 
    virtual void pushSquare() = 0; 
    void push() { 
     pushSquare(); 
    } 
}; 


class Special2 : public Special 
{ 
public: 
    virtual void pushSpecial() = 0; 
    void push() { 
     pushSpecial(); 
    } 
}; 



class SpecialCircle : public Circle2, public Special2 
{ 
public: 
    void pushSpecial() { 
     printf("SpecialCircle::pushSpecial()\n"); 
    } 
    void pushCircle() { 
     printf("SpecialCircle::pushCircle()\n"); 
    } 

}; 

class SpecialSquare : public Square2, public Special2 
{ 
public: 
    void pushSpecial() { 
     printf("SpecialSquare::pushSpecial()\n"); 
    } 
    void pushSquare() { 
     printf("SpecialSquare::pushSquare()\n"); 
    } 

}; 

int main(int argc, char* argv[]) 
{ 
    SpecialCircle sc; 
    SpecialSquare ss; 

    // sc.push(); // can't be called - ambiguous 
    // ss.push(); 
    sc.pushCircle(); 
    ss.pushSquare(); 

    Circle* pCircle = ≻ 
    pCircle->push(); 

    Square* pSquare = &ss; 
    pSquare->push(); 

    Special* pSpecial = ≻ 
    pSpecial->push(); 

    pSpecial = &ss; 
    pSpecial->push(); 


    return 0; 
} 
0

Eh bien, si les cercles spéciaux et normaux peuvent être à la fois les forces appliquées, et le cercle spécial a une autre méthode qui applique forces spéciales, pourquoi ne pas avoir deux interfaces et deux méthodes?

struct Applicable { 
    virtual ~Applicable() { } 

    // if it applies force, better be explicit with naming it. 
    virtual void applyForce() = 0; 
}; 

struct SpecialApplicable { 
    virtual ~SpecialApplicable() { } 

    virtual void applySpecialForce() = 0; 
}; 

struct Shape { 
    virtual ~Shape() { } 
    Size getSize(); 
    Point getPosition(); 
    // ... 
}; 

struct Circle : Shape, Applicable { 
    virtual void applyForce() { /* ... */ } 
} 

struct SpecialCircle : Circle, SpecialApplicable { 
    virtual void applySpecialForce() { /* .... */ } 
}; 

Si elle n'a pas de sens s'il est à la fois une méthode spéciale et appliquer une normale (que le nom de la classe - SpecialCircle - suggère), alors pourquoi ne pas faire même ceci:

struct Circle : Shape, Applicable { 
    virtual void applyForce() { /* ... */ } 
} 

struct SpecialCircle : Circle { 
    // applies force, but specially 
    virtual void applyForce() { /* .... */ } 
}; 

Vous pouvez également mettre le applyForce dans la classe Shape. Cela dépend également de l'environnement dans lequel ces classes sont utilisées. Ce que, en tout cas, vous devriez vraiment éviter est d'avoir la même méthode dans deux classes de base qui apparaissent dans deux réseaux de bases de différences. Parce que cette inévitable conduira à de tels problèmes d'ambiguïté. L'héritage diamond est lorsque vous utilisez l'héritage virtuel. Je crois qu'il y a d'autres bonnes réponses sur stackoverflow expliquant cela. Cela ne s'applique pas à votre problème, car l'ambiguïté provient du fait que la méthode apparaît dans deux sous-objets de classe de base de types différents. (Il ne résout que les cas dans lesquels les classes de base ont le même type: dans ce cas, il fusionnera les classes de base et il ne contiendra qu'un sous-objet de classe de base hérité par l'héritage virtuel)

2

Plutôt que de penser de réutilisation de code par héritage, l'utilisation de mixins vous donnera le code que vous voulez réutiliser sans les problèmes d'héritage multiple.

Si vous n'êtes pas familier avec cette technique, effectuez une recherche sur SO ou Google. Assurez-vous de rechercher à la fois "mixin" et "Curiously Recurring Template Pattern". Il y a des tas de bons articles pour vous aider à démarrer.

1

Lorsque vous devez hériter de plusieurs interfaces avec la même méthode, le compilateur ne peut pas dire lequel vous essayez d'appeler, vous pouvez résoudre ce problème en remplaçant cette méthode et en appelant celle que vous voulez.

class SpecialCircle : public Circle, Special { 
    public: 
    virtual void push() { Special::push(); } 
}; 
class SpecialSquare : public Square, Special { 
    public: 
    virtual void push() { Special::push(); } 
}; 

Mais dans ce cas, je pense que l'approche OO correcte est de prendre en compte le comportement de poussée dans sa propre classe, comme Federico Ramponi ont suggéré.