IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

C++ Discussion :

Double hierarchie d'héritage


Sujet :

C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Futur Membre du Club
    Inscrit en
    Février 2008
    Messages
    4
    Détails du profil
    Informations forums :
    Inscription : Février 2008
    Messages : 4
    Par défaut Double hierarchie d'héritage
    Bonjour,

    Je travaille sur un projet dans l'embarqué et je fait face à un problème de "Dual Inheritance Hierarchies".

    J'ai un premier composant logiciel dont le rôle est de transformer un flot d'octets reçu depuis une interface matérielle en objets de type "Requetes". J'utilise un pattern fabrique abstraite dans ce composant et j'obtient une première hierarchie d'objet : la classe de base IRequete (abstraite) et un ensemble de classes de CRequete_X,Y,Z hérités de IRequete et permettant d'instancier les objets obtenus après désérialisation du flux d'octets. Ce composant n'a pas vocation à connaitre la nature des traitements qui seront effectués concernant ces requêtes.

    J'ai ensuite plusieurs composants dont le rôle va être de traiter ces requetes. Mon premier composant (celui qui crée les objets "requete") fournit un service permettant de venir enregistrer des commandes (ICommande asbtraite) à associer à un objet de type IRequete par rapport à un identifiant (cf code fourni,methode => RegisterCommand).

    Je trouve donc une deuxième hierarchie d'objets (les commandes) donc la classe de base (ICommande) définit le comportement (1 méthode Execute virtuelle pure + 1 paramètre de type IRequete) et les classes dérivées définissent concrètement le traitement à exécuter.

    Mon problème est qu'au final pour associer un objet de type CRequete_X de la hierarchie IRequete à une commande CCommande_X de la hierarchie ICommande, je suis obligé de passer par un downcast.

    Quelqu'un aurait-il une solution plus élégante et différente d'une utilisation du pattern Visiteur?

    J'ai fourni un exemple de code illustrant mon implémentation actuelle.

    Merci
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    #include <iostream>
    using namespace std;
    
    static const unsigned char C_MAX_ID = 5; 
    
    // REMARQUE : TOUTES LES METHODES ONT ETE INLINEES POUR L'EXEMPLE.
    
    // ==== CLASSE DE BASE DES REQUETES ===========
    class IRequete
    {
    public:
    	virtual ~IRequete(){}
    	unsigned char GetId() const {return(_identifiant);}
    protected:
    	IRequete(const unsigned char id):_identifiant(id){}
    	unsigned char _identifiant;
    };
    
    
    // ==== CLASSES DERIVEES DES REQUETES ===========
    class CRequeteX:public IRequete
    {
    public:
    	// constructeur avec parametres arbitraires pour example
    	CRequeteX():IRequete(0),_param1(101),_param2(102){}
    	virtual ~CRequeteX(){}
    
    	// Accesseurs
    	unsigned char GetParam1() const{ return(_param1);}
    	unsigned char GetParam2() const{ return(_param2);}
    
    private:
    	unsigned char _param1;
    	unsigned char _param2;
    };
    
    class CRequeteY:public IRequete
    {
    public:
    	// constructeur avec parametres arbitraires pour exemple
    	CRequeteY():IRequete(1),_param1(101),_param2(102),_param3(103),_param4(104){}
    	virtual ~CRequeteY(){}
    
    	// Accesseurs
    	unsigned char GetParam1() const{ return(_param1);}
    	unsigned char GetParam2() const{ return(_param2);}
    	unsigned char GetParam3() const{ return(_param3);}
    	unsigned char GetParam4() const{ return(_param4);}
    
    private:
    	unsigned char _param1;
    	unsigned char _param2;
    	unsigned char _param3;
    	unsigned char _param4;
    };
    
    
    // ==== CLASSES DE BASE DES COMMANDES ===========
    class ICommande
    {
    public:
    	virtual ~ICommande(){}
    	virtual void Execute() = 0;
    
    	void SetParam(IRequete& refRequeteObj) { _RequeteParametre = &refRequeteObj;} 
    
    protected:
    	IRequete* _RequeteParametre;
    };
    
    
    // ==== CLASSES DERIVEES DES COMMANDES ===========
    class CCommandeX:public ICommande
    {
    public:
    	// Constructeur/Destructeur
    	CCommandeX(){}
    	virtual ~CCommandeX(){}
    
    	virtual void Execute() {
    		cout << "CCommandeX execute" << endl;
    		// Downcast (static / en embarqué contraint par le temps de les faire dynamiques)
    		// A proscrire pour les puristes !!!!!!!
    		CRequeteX& requeteObj =  static_cast<CRequeteX&>(*_RequeteParametre);
    		// Traitement personnalisé de la requête : ici example, on affiche juste
    		// les parametres
    		cout << requeteObj.GetParam1()<< endl;
    		cout << requeteObj.GetParam2()<< endl;
    	}
    };
    
    class CCommandeY:public ICommande
    {
    public:
    	// Constructeur/Destructeur
    	CCommandeY(){}
    	virtual ~CCommandeY(){}
    
    	virtual void Execute() {
    		cout << "CCommandeY execute" << endl;
    		// Downcast (static / en embarqué contraint par le temps de les faire dynamiques)
    		// A proscrire pour les puristes !!!!!!!
    		CRequeteY& requeteObj =  static_cast<CRequeteY&>(*_RequeteParametre);
    		// Traitement personnalisé de la requête : ici exemple, on affiche juste
    		// les parametres
    		cout << requeteObj.GetParam1()<< endl;
    		cout << requeteObj.GetParam2()<< endl;
    		cout << requeteObj.GetParam3()<< endl;
    		cout << requeteObj.GetParam4()<< endl;
    	}
    };
    
    
    // ==== CLASSES DE GESTION DE L'ARRIVEE DES REQUETES : SIMPLIFIEE POUR l'EXAMPLE ===========
    class CInterfaceController
    {
    public:
    	// Singleton
    	static CInterfaceController& GetInstance() { return(_sInstance);}
    	static void ReleaseInstance() { /* static instance */}
    
    	// Association Commande/Requete
    	void RegisterCommand(const unsigned char idRequete,ICommande& refCommande){
    		// pour simplifier pour l'exemple idRequete = id table
    		tableCommandes[idRequete] = &refCommande;
    	}
    
    	// ======= JUSTE POUR L'EXAMPLE =================
    	// Example simplifié de création d'un objet requete et d'execution de la commande associée
    	void ExampleReceptionRequeteX()
    	{
    		// Requete 
    		CRequeteX& requete = *new CRequeteX;
    		unsigned char id = requete.GetId();
    
    		// Preparation & Execution Commande sous sa forme abstraite
    		ICommande* commandeAssociee = tableCommandes[id];
    		commandeAssociee->SetParam(requete);
    		commandeAssociee->Execute();
    		
    	}
    	void ExampleReceptionRequeteY()
    	{
    		// Requete 
    		CRequeteY& requete = *new CRequeteY;
    		unsigned char id = requete.GetId();
    
    		// Preparation & Execution Commande sous sa forme abstraite
    		ICommande* commandeAssociee = tableCommandes[id];
    		commandeAssociee->SetParam(requete);
    		commandeAssociee->Execute();
    	}
    
    	// Destructeur
    	~CInterfaceController(){}
    
    private:
    	// static instance
    	static CInterfaceController _sInstance;
    
    	// Constructeur/Destructeur
    	CInterfaceController(){}
    
    	// Association command request
    	ICommande* tableCommandes[C_MAX_ID];
    
    };
    
    CInterfaceController CInterfaceController::_sInstance;
    
    
    // ================ TEST =========================
    void main()
    {
    	// Creation des commandes
    	CCommandeX& cmdX =  *new CCommandeX;
    	CCommandeY& cmdY =  *new CCommandeY;
    
    	// Enregitrement des commandes
    	CInterfaceController::GetInstance().RegisterCommand(0,cmdX);
    	CInterfaceController::GetInstance().RegisterCommand(1,cmdY);
    
    	// Simulation de la reception de 2 requete X & Y
    	CInterfaceController::GetInstance().ExampleReceptionRequeteX();
    	CInterfaceController::GetInstance().ExampleReceptionRequeteY();
    }

  2. #2
    Membre chevronné

    Inscrit en
    Août 2007
    Messages
    300
    Détails du profil
    Informations forums :
    Inscription : Août 2007
    Messages : 300
    Par défaut
    La conception proposée a un petit coté "évangélique OOP"... la hiérarchie suit-elle de près une méthode externe rigide?

    La première solution pour éviter le downcast serait que les classes requêtes puissent s'exécuter elle-mêmes, mais ça ne colle pas avec la double arborescence. En effet, un simple vector de pointeurs de requêtes suffirait, la commande étant simplement un indice dans cette table (franchement, d'après l'exemple, c'est ça que je ferais, mais il y a peut-être une autre raison d'exister pour la classe Commande).

    Dans tous les cas, le code d'exécution d'une requête nécessite de connaitre des détails intimes de la requête (par exemple dans le code proposé: accès ou non à GetParam4...). Donc la partie où figure ce code doit connaitre le type exact de la requête, et ceci dès la compilation. Si on écarte la solution naturelle citée au début de ce post (exécution par fm virtuelle de requête), et qu'on introduit une classe d'exécution appelée Commande, cela force à ce que la commande sache de quel type il s'agit, ce qui est implémenté ici par un gros drapeau rouge (downcast) signalant un souci de conception à mon avis.
    Je suis de plus chagriné par la procédure RegisterCommand, dont le tableau tableCommandes n'enforce pas le static_cast effectué à l'exécution.

    J'aurais donc tendance à être totalement contre l'existence même de la hiérarchie Commande. Si on insiste quand même pour avoir une classe Commande (le code présenté est très épuré, peut-être y a-t-il une vraie raison ailleurs dans le projet), et si on veut à la fois éviter un cast et aussi un appel virtuel d'une fonction membre execute depuis une classe de base des requetes, on peut utiliser les templates plutôt que de stocker la requête dans la classe de base de commande:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // Solution à un seul appel virtuel:
    struct RequeteX { void execute ( void ) { cout << "param 1"; } };
    struct RequeteY { void execute ( void ) { cout << "param 1, " << "param 2"; } };
    struct Commande_base { virtual void execute ( void ) { throw "ne pas appeler directement"; } };
    template < class R > struct Commande : public Commande_base {
        Commande ( const R& r = R() ) : requete ( r ) {}
        virtual void execute ( void ) { requete.execute(); }
        R requete; };
     
    void bidule...
    {
    vector<Commande_base *> table_commandes;
     
        table_commandes.push_back ( new Commande<RequeteX>() );
        table_commandes.push_back ( new Commande<RequeteY>() );
        table_commandes[0]->execute();
        table_commandes[1]->execute();
    Enfin, si pour des raisons d'épuration de code on a supprimé la véritable raison d'être de Commande et que celle-ci est significativement complexe (je pense par exemple à une commande établissant un contexte, et capable d'exécuter des requetes variées, et des requêtes pouvant déclencher des commandes variées), on peut généraliser l'approche mixte template / OOP:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    struct Commande_base { virtual void execute ( void ) { throw "ne pas appeler directement"; } };
     
    struct Context_A { void set_context ( void ) { cout << "context A"; } };
    struct Context_B { void set_context ( void ) { cout << "context B"; } };
    struct Requete_1 { void use_params  ( void ) { cout << "params 1";  } };
    struct Requete_2 { void use_params  ( void ) { cout << "params 2";  } };
     
    template < class C, class R > struct Commande : public Commande_base { C c_; R r_;
        Commande ( const C& c, const R& r ) : c_ ( c ), r_ ( r) {}
        virtual void execute ( void ) { c_.set_context(); r_.use_params(); } };
    template < class C, class R > Commande<C,R> * make_command ( const C& c, const R& r ) {
        return ( new Commande<C,R>(c,r) ); }
     
    void f ...
    {
    vector<Commande_base *> table_commandes;
     
        table_commandes.push_back ( make_command ( Context_A(), Requete_2() ) );
        table_commandes.push_back ( make_command ( Context_B(), Requete_2() ) );
        table_commandes.push_back ( make_command ( Context_B(), Requete_1() ) );
        table_commandes[0]->execute();
        table_commandes[1]->execute();
        table_commandes[2]->execute();
    Noter que les classes Requete_X et Contexte_Y ne sont pas des classes dérivées: on n'a bien que le seul appel virtuel imposé par le stockage hétérogène, tout le reste est déterminé à la compilation.

  3. #3
    Futur Membre du Club
    Inscrit en
    Février 2008
    Messages
    4
    Détails du profil
    Informations forums :
    Inscription : Février 2008
    Messages : 4
    Par défaut
    La conception proposée a un petit coté "évangélique OOP"... la hiérarchie suit-elle de près une méthode externe rigide?
    Je ne comprends pas très bien le sens de la remarque

    Il est vrai que mon mail premier mail manque peut-être de précisions pour comprendre ce qui m'amène à une solution "évangélique OOP". Sans pour autant entrer dans le détail, le projet est codé par plusieurs développeurs et on essaye d'avoir une conception qui assure un périmètre bien marqué à chaque développeur. Pour en revenir à l'exemple, on a effectivement défini un composant logiciel dont la mission est de transformer un flot d'octets reçu sur une interface en objets requêtes (pouvant être assimilés à des PODs ou bien à des pures entités). Ces objets ont un sens au niveau métier (requêtes de configuration, de monitoring,etc...). Je voyais ce composant comme un simple composant d'interface qui n'a pas de connaissance a priori de ce qui va être fait sur ces objets. D'ailleurs dans le reste de l'appli, il y a un ensemble de composants logiciels dont le rôle est, entre autre, de traiter ces requêtes (ces composants ont donc intêrêt à pouvoir exploiter pleinement tous les paramètres de ces requêtes). Ce qu'il faut préciser aussi, c'est que plusieurs composants peuvent être associés à une même requête pour des traitement différents mais aussi que l'appli peut changer de mode de fonctionnement et dans ce cas, les commandes à associer aux requêtes peuvent changer. Tout ceci pour dire que le composant d'interface , pour être le plus générique possible ne devrait pas connaître les potentiels consommateurs de requêtes ni les traitements métiers effectués. D'où ma fameuse méthode "RegisterCommand" dont il existe l'équivalent "UnregisterCommand" pour gérer l'association dynamique des utilisateurs et des requêtes. Je pense ici éclairer un peu le choix de conception qui somme toute ne me parait forcément parfait au niveau implémentation sinon je n'aurais pas fait ce post.

    La première solution pour éviter le downcast serait que les classes requêtes puissent s'exécuter elle-mêmes, mais ça ne colle pas avec la double arborescence. En effet, un simple vector de pointeurs de requêtes suffirait, la commande étant simplement un indice dans cette table (franchement, d'après l'exemple, c'est ça que je ferais, mais il y a peut-être une autre raison d'exister pour la classe Commande).
    J'avais effectivement pensé à inclure l'exécution dans la requête. Cependant, par rapport aux aspects reconfiguration des commandes au runtime, je pense être obligé de passer par un pattern fabrique abstraite pour m'en sortir: Le composant d'interface recoit le flux d'octet. Par rapport à l'identifiant de requête, il trouve la bonne fabrique "abstraite" de "requête exécutable". Cette fabrique lui permet de créer une "requête exécutable" abstraite sur laquelle sont déclenchées les méthodes abstraites InitilizeFromFlow et Execute. Il n'y a plus de downcast, par contre les consommateurs de requêtes doivent non plus enregistrer des commandes mais des fabriques de commandes (on rajoute un niveau facilement templatisable).

    Dans tous les cas, le code d'exécution d'une requête nécessite de connaitre des détails intimes de la requête (par exemple dans le code proposé: accès ou non à GetParam4...). Donc la partie où figure ce code doit connaitre le type exact de la requête, et ceci dès la compilation. Si on écarte la solution naturelle citée au début de ce post (exécution par fm virtuelle de requête), et qu'on introduit une classe d'exécution appelée Commande, cela force à ce que la commande sache de quel type il s'agit, ce qui est implémenté ici par un gros drapeau rouge (downcast) signalant un souci de conception à mon avis.
    Je suis de plus chagriné par la procédure RegisterCommand, dont le tableau tableCommandes n'enforce pas le static_cast effectué à l'exécution.
    Je ne comprend pas bien le sens de la dernière phrase: enforcé le static_cast? !!!
    Concernant la connaissance par la commande du type exact de la requête, elle me parait nécessaire car la commande va exécuter un traitement "métier" conditionné par les données "métier" de la requête.Ce qui est effectivement plus dérangeant c'est de créer un lien entre 2 hierarchies de classes au niveau des types abstraits et de devoir effectuer un cast pour retrouver ce lien au niveau des classes d'implémentation.
    Toutefois, j'ai un peu de moins de complexes aujourd'hui à utiliser un downcast lorsque je vois des cadors comme Andrei Alexandrescu ou Robert C. Martin en utiliser; le premier dans son livre Moder C++ Design pour traiter le visiteur acyclic et le second qui propose le pattern (que je viens de découvrir) du nom de "RUNGS OF A DUAL HIERARCHY" (article Design Patterns for Dealing with Dual Inheritance Hierarchies in C++) et qui correspond plus ou moins à la situation dans laquelle je me trouve.


    J'aurais donc tendance à être totalement contre l'existence même de la hiérarchie Commande. Si on insiste quand même pour avoir une classe Commande (le code présenté est très épuré, peut-être y a-t-il une vraie raison ailleurs dans le projet), et si on veut à la fois éviter un cast et aussi un appel virtuel d'une fonction membre execute depuis une classe de base des requetes, on peut utiliser les templates plutôt que de stocker la requête dans la classe de base de commande:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // Solution à un seul appel virtuel:
    struct RequeteX { void execute ( void ) { cout << "param 1"; } };
    struct RequeteY { void execute ( void ) { cout << "param 1, " << "param 2"; } };
    struct Commande_base { virtual void execute ( void ) { throw "ne pas appeler directement"; } };
    template < class R > struct Commande : public Commande_base {
        Commande ( const R& r = R() ) : requete ( r ) {}
        virtual void execute ( void ) { requete.execute(); }
        R requete; };
     
    void bidule...
    {
    vector<Commande_base *> table_commandes;
     
        table_commandes.push_back ( new Commande<RequeteX>() );
        table_commandes.push_back ( new Commande<RequeteY>() );
        table_commandes[0]->execute();
        table_commandes[1]->execute();
    La solution template est effectivement élégante mais elle oblige à connaître les liens au compil time.

  4. #4
    Membre chevronné

    Inscrit en
    Août 2007
    Messages
    300
    Détails du profil
    Informations forums :
    Inscription : Août 2007
    Messages : 300
    Par défaut
    Pour résumer: je ne pense pas qu'il y ait d'implémentation C++ propre de la conception que vous proposez. Cette conception semble justifiée par ailleurs, donc allez-y. Cependant, il y a un gros problème de maintenance future dans la fonction registerCommand telle que décrite. Si j'étais en charge du projet je laisserais sans doute la conception avec downcast à l'exécution si l'implémenteur est toujours motivé après exposé des alternatives. Mais j'imposerais un contrôle très fort au remplissage de la table (qui n'est surement pas soumis aux même contraintes temps-réel, donc les contrôles sont possibles). Dans l'état actuel, on peut enregistrer une commande qui sera "downcastée" de façon erronée. Si cela peut arriver, cela arrivera, et d'après la loi de la tartine, ce sera dans 4 ans quand vous aurez changé de boite

    Par contre, l'approche mixte template / OOP est une méthode très forte et générale, s'appliquant brillamment aux conteneurs hétérogènes et aux couplages de classes. Je la recommande dans votre problème. Sans être familier avec celui-ci, je ne vois en effet pas du tout en quoi le fait de faire une classe Commande<RequeteX> est moins bien que CommandeX contenant un downcast vers RequeteX: dans les deux cas on doit connaitre les deux types ainsi que leur association à la compilation, mais dans le cas mixte template /OOP, on évite de la duplication de code, et on ne pose aucune contrainte sur d'éventuels conteneurs hétérogènes. Mieux: choisir une hiérarchie duale est très étroit, et peu susceptible de s'adapter aux changements de certains projets. L'outil template peut par contre être généralisé, fortement réaménagé et étendu sans retouche de code (requête jamais prévue au départ, ne correspondant à aucune commande). Pour une répartition parfaite des informations dans la meilleure classe possible, on peut même considérer des classes avec Executeur<Commande, Contexte, Requete, Mode> etc... avec spécialisation partielle. On ne peut pas plus souple et orthogonal, et on n'est pas obligé dès le départ de mettre tout! (on peut toujours rajouter Environnement = x64 en dernier paramètre template dans 2 ans quand on change d'architecture...)

    Une hiérarchie duale me parait un aimant à erreur et un cout de maintenance et d'écriture avéré, et sa nécessité n'est pas apparente sans connaitre votre problème précisément (comme indiqué dans mon post initial, il doit bien y avoir une raison externe au code proposé pour l'existence même de la hiérarchie Commande). J'ai bien lu dans le lien proposé que quand on ne peut absolument pas faire autrement, il faut le faire...
    Ma foi, j'ai aussi été forcé à des choses atroces récemment (je travaille aussi dans l'embarqué, en ce moment il faut que je passe 22 Gbits/s d'un GPU vers une machine, en gérant les collisions sur PCI... c'est pas toujours beau). Sincères condoléances

  5. #5
    Futur Membre du Club
    Inscrit en
    Février 2008
    Messages
    4
    Détails du profil
    Informations forums :
    Inscription : Février 2008
    Messages : 4
    Par défaut
    Merci ac_wingless pour tes conseils. Suite aux discussions, j'ai un peu modifié le code initial pour regrouper l'initialisation et l'exécution au sein de la requête. En revanche, je tiens à la généricité du composant d'interface et le code suivant permet de l'obtenir sans downcast. Le composant d'interface reste inchangé (pas de recompilation) lorsque de nouvelles requêtes seront créées. L'ajout d'une requête se fera par la création de la classe associée permettant de définir comment l'initialiser à partir du flot d'octets et les différents utilisateurs potentiel de cette requête n'auront qu'à définir les exécuteurs associés.
    Je sais qu'il y a encore des imperfections mais la solution est-elle plus élégante?

    Execution requete activation sans downcast
    traitement activation _param1:
    traitement activation _param2:☺
    traitement activation _param3:2
    traitement activation _param4:3
    Execution USer1 requete asservissement sans downcast
    traitement user1 _param1:5
    traitement user1 _param2:6
    Execution User2 requete asservissement sans downcast
    traitement user2 _param1:5
    traitement user2 _param2:6

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
     
    #include <iostream>
    using namespace std;
     
    // REMARQUE : BEAUCOUP DE METHODES ONT ETE INLINEES POUR L'EXEMPLE.
     
    // Identifiant requêtes
    enum eIdentifiantsRequetes
    {
        E_ACTIVATION,
        E_DESACTIVATION,
        E_CONFIG_ASSERVISSEMENT,
        E_CONFIG_POWER
        //etc....
    };
     
    // ==== CLASSE DE BASE DES REQUETES INITIALISABLES DEPUIS UN FLUX & EXECUTABLES ===========
    class IRequeteInitialisableExecutable
    {
    public:
    	virtual ~IRequeteInitialisableExecutable(){}
    	unsigned char GetId() const {return(_identifiant);}
     
        // Initialisation depuis un flux
        virtual unsigned short Initialize(const unsigned char* byteFlow) = 0;
        // Execution
    	virtual void Execute() = 0;
     
    protected:
    	IRequeteInitialisableExecutable(const unsigned char id):_identifiant(id){}
    	unsigned char _identifiant;
    };
     
     
    // ==== CLASSES DERIVEES DES REQUETES INITIALISABLES & EXECUTABLES ===========
    class CRequeteConfigAsservissement:public IRequeteInitialisableExecutable
    {
    public:
    	// constructeur avec parametres arbitraires pour example
    	CRequeteConfigAsservissement():IRequeteInitialisableExecutable(E_CONFIG_ASSERVISSEMENT){}
    	virtual ~CRequeteConfigAsservissement(){}
     
        virtual unsigned short Initialize(const unsigned char* byteFlow) 
        {
            // exemple simple d'initialisation à partir du flux
            _param1 = byteFlow[1]; 
            _param2 = byteFlow[2];
            return(3); // 3 octets consommés dans le flux (params + identifiant)
        }
     
    protected:
    	unsigned char _param1;
    	unsigned char _param2;
    };
     
     
    class CRequeteActivation:public IRequeteInitialisableExecutable
    {
    public:
    	// constructeur avec parametres arbitraires pour exemple
    	CRequeteActivation():IRequeteInitialisableExecutable(E_ACTIVATION){}
    	virtual ~CRequeteActivation(){}
     
        virtual unsigned short Initialize(const unsigned char* byteFlow) 
        {
            // exemple simple d'initialisation à partir du flux
            _param1 = (byteFlow[1] << 4) & 0x0F; 
            _param2 = (byteFlow[1] & 0x0F); 
            _param3 = byteFlow[2];
            _param4 = byteFlow[3];
            return(4); // 4 octets consommés dans le flux (params + identifiant)
        }
     
    	// Accesseurs
    	unsigned char GetParam1() const{ return(_param1);}
    	unsigned char GetParam2() const{ return(_param2);}
    	unsigned char GetParam3() const{ return(_param3);}
    	unsigned char GetParam4() const{ return(_param4);}
     
    protected:
    	unsigned char _param1;
    	unsigned char _param2;
    	unsigned char _param3;
    	unsigned char _param4;
    };
     
     
     
    // ==== TEMPLATE DE DEFINITION DES EXECUTEURS DE REQUETES EXECUTABLES & INITIALISABLE ===========
    template <class TRequeteInitialisableExecutable,int userScope>
    class TReqExecuter:public TRequeteInitialisableExecutable 
    {
    public:
    	// Constructeur/Destructeur
    	TReqExecuter(){}
    	virtual ~TReqExecuter(){}
     
    	virtual void Execute();
    };
     
    // ==== DEFINITION DES EXECUTEURS A PARTIR DU TEMPLATE ===========
    typedef TReqExecuter<CRequeteConfigAsservissement,1> CRequeteExecuterConfigAsservissementUser1;
    typedef TReqExecuter<CRequeteConfigAsservissement,2> CRequeteExecuterConfigAsservissementUser2;
    void CRequeteExecuterConfigAsservissementUser1::Execute()
    {
        cout << "Execution USer1 requete asservissement sans downcast " << endl;
        cout << "traitement user1 _param1:" << _param1 << endl;
        cout << "traitement user1 _param2:" << _param2 << endl;
    }
    void CRequeteExecuterConfigAsservissementUser2::Execute()
    {
        cout << "Execution User2 requete asservissement sans downcast " << endl;
        cout << "traitement user2 _param1:" << _param1 << endl;
        cout << "traitement user2 _param2:" << _param2 << endl;
    }
     
    typedef TReqExecuter<CRequeteActivation,1> CRequeteExecuterActivation;
    void CRequeteExecuterActivation::Execute()
    {
        cout << "Execution requete activation sans downcast " << endl;
        cout << "traitement activation _param1:" << _param1 << endl;
        cout << "traitement activation _param2:" << _param2 << endl;
        cout << "traitement activation _param3:" << _param3 << endl;
        cout << "traitement activation _param4:" << _param4 << endl;
    }
     
    // ==== BUILDER D'EXECUTEUR ===========
    class IRequeteExecuterBuilder
    {
    public:
    	virtual ~IRequeteExecuterBuilder(){}
    	virtual IRequeteInitialisableExecutable& BuildReqExecuter() = 0;
    };
     
    // ====  TEMPLATE DE DECLARATION DES BUILDERS D'EXECUTEURS ===========
    template <class TRequeteExecuter>
    class TReqExecuterBuilder:public IRequeteExecuterBuilder
    {
    public:
    	// Constructeur/Destructeur
    	TReqExecuterBuilder(){}
    	virtual ~TReqExecuterBuilder(){}
     
        // Creation de la requete executable
        virtual IRequeteInitialisableExecutable& BuildReqExecuter() {return *new TRequeteExecuter;}
    };
     
     
    // ==== CLASSES DE GESTION DE L'ARRIVEE DES REQUETES : SIMPLIFIEE POUR l'EXAMPLE ===========
    class CInterfaceController
    {
    public:
        CInterfaceController(){for(int i=0;i<C_MAX_BUILDER;i++)_tableBuilder[i] = 0;}
        ~CInterfaceController();
     
    	// Association Commande/Requete
    	void RegisterBuilderRequeteExecutable(const unsigned char idRequete,IRequeteExecuterBuilder& refBuilder)
        {
            // Recherche d'une place pour un builder
            for(int i=0;i<C_MAX_BUILDER;i++)
            {
                // pour simplifier pas de traitement d'erreur
     
                if(_tableBuilder[i] == 0)
                {
                    _tableBuilder[i] = &refBuilder;
                    _identifiantRequeteBuilder[i] = idRequete;
                    break;
                }
            }
    	}
     
        // Traitement d'une requête sous forme de flow 
        void ReceiveRequestFlow(const unsigned char* flow);
     
    private:
    	// Table des builders
        static const unsigned char C_MAX_BUILDER = 10;
    	IRequeteExecuterBuilder* _tableBuilder[C_MAX_BUILDER];
        unsigned char _identifiantRequeteBuilder[C_MAX_BUILDER];
    };
     
    // Traitement de la reception d'une requete en flux d'octets
    void CInterfaceController::ReceiveRequestFlow(const unsigned char* flow)
    {
        // Extraite identifiant requete : 1er octet
        unsigned char identifiantRequete = *flow;
     
        // Obtention des builder associés
        for(int i=0;i<C_MAX_BUILDER;i++)
        {
            if((_tableBuilder[i] != 0) && (_identifiantRequeteBuilder[i] == identifiantRequete))
            {
                IRequeteInitialisableExecutable& requeteExecutable = _tableBuilder[i]->BuildReqExecuter();
                requeteExecutable.Initialize(flow);
                requeteExecutable.Execute();
            }
        }
    }
     
     
    // ================ TEST =========================
    void main()
    {
        unsigned char flow[10];
     
        CInterfaceController& intf = *new CInterfaceController;
     
        // Simulation des users
        // User1 traite les requetes d'activation et d'asservissement
        intf.RegisterBuilderRequeteExecutable(E_ACTIVATION,
                                              *new TReqExecuterBuilder<CRequeteExecuterActivation>);
     
        intf.RegisterBuilderRequeteExecutable(E_CONFIG_ASSERVISSEMENT,
                                              *new TReqExecuterBuilder<CRequeteExecuterConfigAsservissementUser1>);
     
     
        // User2 traite les requetes d'asservissement
        intf.RegisterBuilderRequeteExecutable(E_CONFIG_ASSERVISSEMENT,
                                              *new TReqExecuterBuilder<CRequeteExecuterConfigAsservissementUser2>);
     
     
        // Simulation de la réception de 2 requetes
        flow[0] = E_ACTIVATION;
        flow[1] = 0x31;
        flow[2] = 0x32;
        flow[3] = 0x33;
     
        intf.ReceiveRequestFlow(flow);
     
        flow[0] = E_CONFIG_ASSERVISSEMENT;
        flow[1] = 0x35;
        flow[2] = 0x36;
        intf.ReceiveRequestFlow(flow);
    }

  6. #6
    Membre chevronné

    Inscrit en
    Août 2007
    Messages
    300
    Détails du profil
    Informations forums :
    Inscription : Août 2007
    Messages : 300
    Par défaut
    Là j'aime beaucoup! Je trouve ça en fait très élégant.
    La différence de traitement entre l'initialisation et l'exécution se justifie par la présence de l'utilisateur nécessaire à l'exécution. J'ai du mal à imaginer mieux. Il aurait sans doute été possible d'appliquer la même méthode que vous avez appliqué à l'exécution pour se débarrasser du dernier appel virtuel dans l'initialisation:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     
    template <class TRequeteInitialisableExecutable>
    class TReqInitializer:public TRequeteInitialisableExecutable 
    {
    ... même technique que pour l exécution
     
    template <class TRequeteInitialisableExecutable,int userScope>
    class TReqExecuter:public TReqInitializer<TRequeteInitialisableExecutable>
    {
    ... ne rien changer
    néanmoins, l'utilisation OOP pour l'initialisation est tout à fait légitime, et je pense qu'un appel virtuel est suffisamment proche de gratuit pour que ça n'ait aucune influence.

    Il y a une fuite de mémoire causée par
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    virtual IRequeteInitialisableExecutable& BuildReqExecuter() {return *new TRequeteExecuter;}
    et non récupérée par:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    IRequeteInitialisableExecutable& requeteExecutable = _tableBuilder[i]->BuildReqExecuter();
    Plus génant que la fuite mémoire, l'allocation dynamique durant l'exécution ReceiveRequestFlow ? (je ne connais pas vos contraintes de temps réel).

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. Double héritage (virtuel et non virtuel)
    Par oodini dans le forum Langage
    Réponses: 9
    Dernier message: 09/11/2012, 14h15
  2. Double héritage de classe template, quelques questions
    Par the_angel dans le forum Langage
    Réponses: 2
    Dernier message: 29/07/2012, 12h26
  3. [1.1] double héritage
    Par CUCARACHA dans le forum VB.NET
    Réponses: 12
    Dernier message: 05/11/2007, 09h54
  4. Double héritage, interface
    Par faulk dans le forum Langage
    Réponses: 7
    Dernier message: 18/04/2007, 02h46
  5. Réponses: 3
    Dernier message: 12/06/2002, 21h15

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo