J'essaie d'utiliser la classe de base abstrait C ++ dans la même manière que l'interface Java. Supposé que nous avons suivi des classes d'interface avec uniquement des fonctions virtuelles pures: et j'essaie d'implémenter carré et rectangle de la manière suivante: p> Où Je pourrais compiler le code si je hérite virtuellement Une autre solution peut être de faire des interfaces indépendantes les unes des autres, c'est-à-dire à faire Donc, ma question est donc, y a-t-il un autre modèle de conception en C ++ pour résoudre ce genre de héritage parallèle entre les classes d'interface et les classes de mise en œuvre, sans nécessiter d'héritage virtuel? p> (modifier la clarification: l'exemple de code ci-dessus ne sont que mon illustration factice sur l'héritage parallèle entre les interfaces et les implémentations. Je comprends qu'il y a de meilleurs moyens de Implémentez des formes mais mon problème n'est pas sur la manière de mettre en œuvre des formes.) p> p> rectangleimpl code> hérite à la fois
Squareimpl code> et
rectangle code> pour réutiliser, dire,
Squareimpl :: zone () code>. Cependant, lorsque j'essaie de compiler, deux problèmes surviennent: premièrement, toutes les méthodes de
Squareimpl CODE> ne sont pas héritées correctement et je dois réprimer manuellement
rectangleimpl :: zone () code> et < Code> rectangleimpl :: setlength () code>. Deuxièmement, cela introduit toujours le problème de diamant que code> forme code> est une base ambiguë de
rectangleimpl code>. P>
carré code> à partir de
forme code>, mais je ne pense pas que la performance s'ajoutera avec des interfaces plus dérivées ajoutées. Également étrangement,
rectangleimpl code> ne hériter toujours pas
Squareimpl :: setlength () code> Bien que
Squareimpl :: Zone () code> est bien hérité. (Ignorer l'aspect pratique ici) P>
carré code> non hérité de
de forme code>. Mais cela me fera perdre l'accès aux méthodes de code> code> si je défini des fonctions qui prennent un pointeur code> carré * code>. Il rendra aussi static_cast impossible entre
forme code> et
carré code>. P>
6 Réponses :
carré n'est pas rectangle et le rectangle n'est pas carré. La seule chose qu'ils ont en commun sont qu'ils sont des formes. Donc:
class Square : public Shape {...}; class Rectangle : public Shape {...};
Mon exemple n'est qu'une illustration du problème. Je comprends qu'il y a de meilleurs moyens de mettre en œuvre la forme, mais je viens de créer les classes pour montrer mon point de héritage parallèle.
Ah, mauvaise illustration. Hmm, je pense que vous devriez supprimer Public Squareimpl Code> à partir de
Classe RectangleImpl Code> et mettre le code commun dans certaines fonctions statiques que
Squareimpl code> et
et
RectangleImpl code> peut appeler.
-1 Je sens que vous n'avez pas vraiment répondu au problème des Askers, je pense qu'il est clair qu'il obtient les bases ici
Il essaie de réutiliser Squareimpl :: Zone () de RectangleImpl. C'est impossible et il a clairement mélangé quelque chose.
Buthe reconnaît que c'est une chose étrange (sans signification) à faire ici, donc il comprend les problèmes.
Je pense que vous devriez rechercher un héritage virtuel ici de sorte que vous n'avez qu'une seule instance de forme sous tout cela - cela semble évident d'un point de vue sémantique - c'est-à-dire la forme que carré et rectangleimpl est clairement la même forme. p>
Pas vraiment sûr de la question de la performance que vous mentionnez - cela ne me semble pas un problème (dans le sens où leur n'est pas une surcharge supplémentaire autre que celle d'appeler toute V-Fonction). Je ne vois pas pourquoi vous ne pouvez pas accéder à la bordure de la place - difficile à voir pourquoi vous pourriez rencontrer cela et vous n'avez pas la source pour les implémentations. P>
Vous n'êtes pas de loin la première qui a rencontré ce problème. Voir un Square n'est pas un rectangle pour en donner un Exemple. P>
Votre problème est rectangle-> Square-> La forme ne connaît rien sur la Squareimpl afin qu'il ne puisse pas utiliser ces fonctions pour satisfaire ses exigences de fonction abstraites. P>
Le moyen le plus simple est de ne pas mélanger votre interface et votre héritage de mise en œuvre lorsqu'ils sont étroitement liés comme ceci. Faire des rectangles hériter de l'interface carrée et rectangle. Si Squareimpl et RectangleImpl se répliquent trop du code de l'autre, utilisez une classe qui fonctionne tout cela et possède une fonction de membre dans chaque mise en œuvre. P>
Ce que vous avez ici est le cas du problème de diamant , qui peut se produire dans n'importe quel OO langue qui permet de multiples héritage. Ceci, au fait, est une raison pour laquelle les concepteurs de Java ont décidé de ne pas avoir de héritage multiple et ont créé la notion d'interface. P>
The Way C ++ offres au problème du diamant est héritage virtuel . P>
Et, comme le soulignait Codymanix, le carré et le rectangle sont un exemple notoirement mauvais pour la conception orientée objet, car dans la mesure où OO est concerné Un carré n'est pas un rectangle . P>
Couple de plus de points. Premièrement, le terme pour ce que vous faites ici est héritage multiple em>, pas "héritage parallèle". Deuxièmement, dans ce cas particulier, il n'a guère aucun sens d'avoir un Vous êtes certainement peut utiliser une classe abstrait C ++ comme une interface Java, mais la plupart du temps, il n'y a aucune raison de cela. Des interfaces ont été ajoutées à Java précisément comme moyen de contourner le manque de héritage multiple. En C ++, vous pouvez simplement aller de l'avant et utiliser plusieurs héritages, bien que vous devriez toujours le faire très judicieusement. p> carré de classe code> et un
Squareimpl de classe code>. Si vous pensez avoir des implémentations différentes de code> carré code>, vous devez simplement avoir une classe de base qui fournit une implémentation par défaut et des fonctions virtuelles pouvant être remplacées par une classe dérivée si nécessaire. En d'autres termes, vous devez rouler
carré code> et
Squareimpl code> en une classe avec des fonctions virtuelles. P>
J'ai utilisé les formes comme exemple de mise en œuvre de quelque chose potentiellement complexe derrière une classe d'interface C ++. Dans ce cas, Square Code> est assez simple pour avoir sa mise en œuvre et une interface combinées, ce qui pourrait ne pas être le cas pour d'autres situations. Quoi qu'il en soit, merci pour la note que la forme est un exemple notoirement mauvais. Je suppose que je vais utiliser Foobar dans mes exemples la prochaine fois.
Votre suggestion de rouler Square et Squareimpl en une seule classe fonctionne dans ce cas, mais le problème de Soares se produit fréquemment lors de la rédaction de code d'injection de dépendance. Pour DI, vous aurez toujours 2 classes Squareimpl qui ont des implémentations très différentes (une est réelle, l'un est un simple objet simulé). Il y a un code zéro à partager, il est donc parfaitement logique de les faire hériter de la même interface. FYI La solution que je suis allé avec DI est de simplement faire un héritage virtuel à partir de la pile d'héritage de l'interface. Un peu de duplication, mais au moins cela fonctionne: /
Après avoir repensé une nuit et se référant à la solution Sean fournie à Ici, je redéfinisse le problème pour être plus abstrait pour éviter la confusion que nous avions sur les formes. Nous avons une interface Pour résoudre le problème, au lieu de mettre directement FOO et de la barre de méthodes spécifiques dans les interfaces Dérivées toujours, tous les codes ne peuvent pas être éliminés pour dériver tout Barres de foos librement. Nous devons toujours écrire des implémentations indépendantes de ball code> qui peut rouler, une interface
fooball code> contenant des méthodes spécifiques FOO et une interface
foobarball code> qui est également un
FOOBALL code> et contient des méthodes spécifiques et spécifiques à la barre FOO. Identique au problème initial, nous avons une implémentation code> FOOBALL CODE> et nous souhaitons la dériver de manière à couvrir les méthodes spécifiques à la barre. mais hériter à la fois de l'interface et de la mise en œuvre introduira l'héritage de diamant. P>
BALLE CODE>, je mets un seul Méthode dans une interface dérivée
FOOBALL code> qui convertit l'objet en un objet
FOO code> via la méthode
tofoo () code>. De cette façon, les implémentations peuvent se mélanger dans l'interface indépendante
FOO code> et
Barre code> sans introduire héritage de diamant. P>
ball code>,
fooball code> et
foobarball code> qui ne héritent pas de l'autre. Mais nous pouvons utiliser le motif composite pour envelopper le réel
FOO code> et
bar code> implémenté différemment. De cette façon, nous pouvons toujours éliminer beaucoup de code si nous avons beaucoup d'implémentations de FOO et de BAR. P>
#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;
}
"Où RectangleImpl hérite à la fois carréfer et rectangle pour réutiliser, dire, Squareimpl :: Zone ()" => hériter pour être réutilisé, ne pas réutiliser.
Dupe de Stackoverflow.com/Questtions/249500/... ?
L'héritage non public du rectangle est-il destiné?