6
votes

Héritage parallèle entre les classes d'interface et les classes de mise en œuvre en C ++

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: xxx

et j'essaie d'implémenter carré et rectangle de la manière suivante: xxx

rectangleimpl hérite à la fois Squareimpl et rectangle pour réutiliser, dire, Squareimpl :: zone () . Cependant, lorsque j'essaie de compiler, deux problèmes surviennent: premièrement, toutes les méthodes de Squareimpl ne sont pas héritées correctement et je dois réprimer manuellement rectangleimpl :: zone () et < Code> rectangleimpl :: setlength () . Deuxièmement, cela introduit toujours le problème de diamant que forme est une base ambiguë de rectangleimpl .

Je pourrais compiler le code si je hérite virtuellement carré à partir de forme , mais je ne pense pas que la performance s'ajoutera avec des interfaces plus dérivées ajoutées. Également étrangement, rectangleimpl ne hériter toujours pas Squareimpl :: setlength () Bien que Squareimpl :: Zone () est bien hérité. (Ignorer l'aspect pratique ici)

Une autre solution peut être de faire des interfaces indépendantes les unes des autres, c'est-à-dire à faire carré non hérité de de forme . Mais cela me fera perdre l'accès aux méthodes de si je défini des fonctions qui prennent un pointeur carré * . Il rendra aussi static_cast impossible entre forme et carré .

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?

(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.)


3 commentaires

"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é?


6 Réponses :


0
votes

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 {...};


5 commentaires

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 à partir de Classe RectangleImpl et mettre le code commun dans certaines fonctions statiques que Squareimpl et et RectangleImpl 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.



0
votes

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.

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.


0 commentaires

2
votes

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.


0 commentaires

0
votes

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.

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.


0 commentaires

4
votes

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.

The Way C ++ offres au problème du diamant est héritage virtuel .

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 .

Couple de plus de points. Premièrement, le terme pour ce que vous faites ici est héritage multiple , pas "héritage parallèle". Deuxièmement, dans ce cas particulier, il n'a guère aucun sens d'avoir un carré de classe et un Squareimpl de classe . Si vous pensez avoir des implémentations différentes de carré , 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é et Squareimpl en une classe avec des fonctions virtuelles.

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.


2 commentaires

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 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: /



1
votes

Après avoir repensé une nuit et se référant à la solution Sean fournie à Vous cherchez une meilleure façon que l'héritage virtuel en C ++ , je suis sorti avec la solution suivante.

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 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>

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 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>

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>, 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;

}


0 commentaires