2010-01-20 5 views
1

Mon application contient plusieurs modules (grandes classes) comme le réseau io, le stockage de données, les contrôles, etc. Certains d'entre eux peuvent coopérer. Quel serait un bon moyen de déclarer et de lier les modules? Je vois plusieurs possibilités:Application C++: conception de modules

1) Tous les modules déclarés mondiale, nous avons donc dans main.cpp

#include ... 
ModuleA a; 
ModuleB b; 
ModuleC c; 

et si un veut parler au module c par exemple, nous aurons à a.cpp ce qui suit:

#include "c.hpp" 
extern ModuleC c; 

et ainsi de suite;

2) Tous les modules déclarés dans main(), ils sont donc locaux. Les liaisons sont réalisées dans les constructeurs:

int main() { 
ModuleC c; 
ModuleA a(c); 
ModuleB b; 
} 

mais de cette manière, il est difficile de lier des objets qui veulent de l'autre (a (c), c (a))

3) Première phase: déclarer localement , deuxième phase: lier avec des pointeurs:

int main() { 
ModuleA a; 
ModuleB b; 
ModuleC c; 
a.Connect(&b); 
b.Connect(&a); 
c.Connect(&a, &b); 
} 

Y a-t-il un meilleur moyen? Je voudrais que ce soit dans le style cpp. La troisième façon de garder les pointeurs est un peu confuse (bien qu'il n'y aura pas de problèmes avec la validité des pointeurs bien que les modules vivent tout le temps, mais encore) et a une initialisation en deux phases, ce qui ne garantit pas que nous n'oublierons pas init un module, et oops - un pointeur invalide. La deuxième façon (comme je le pense) peut planter toute l'idée si certains objets auraient besoin de liaison croisée. La première voie semble naturelle (puisque les modules représentent l'application elle-même), mais n'est-ce pas un mauvais style? J'ai vu des projets où les modules ont été déclarés dans une classe d'univers et ils coopèrent via cet univers comme s'ils étaient tous globaux. Qu'est-ce que tu penses?

Merci.

+0

En général, grande classe == mauvaise conception –

Répondre

0

D'un point de vue informatique, vous voulez idéalement pour diminuer le couplage autant que possible. D'un point de vue général du développement, vous voulez réduire la quantité de code compilé lorsque vous effectuez une modification quelque part. Donc, pour résoudre ce problème, vous devez utiliser des interfaces et la classe 'universe'.

main() 
{ 
    Universe my_app; 
    ModuleA a (my_app); 
    ModuleB b (my_app); 
    ModuleC c (my_app); 
} 

class ModuleA : public ModuleAInterface 
{ 
public: 
    ModuleA (Universe &my_app) : m_my_app (my_app) 
    { 
    m_my_app.Register (this); 
    } 
private: 
    Universe &m_my_app; 
} 

// etc... 

class Universe 
{ 
public: 
    template <class T> 
    void Register (T *module) 
    { 
    m_modules [T::ModuleID] = module; 
    } 

    template <class T> 
    T *Module() 
    { 
    return reinterpret_cast <T *> (m_modules [T::ModuleID]); 
    } 
private: 
    std::map <int, void *> m_modules; 
}; 

Notez que cela signifie que les constructeurs de modules ne peuvent pas appeler des fonctions dans d'autres modules que l'ordre de construction est effectivement définie.

Pour utiliser ce qui précède, ModuleA pourrait avoir une fonction comme:

void ModuleA::SomeFunction() 
{ 
    ModuleBInterface *b = m_my_app.Module <ModuleBInterface>(); 
} 

Donc, mettre tous ensemble, voici un exemple de programme qui compile et fonctionne à l'aide DevStudio 2005 (créer un défaut, l'application de la console vide) :

#include <map> 
#include <iostream> 

class Universe 
{ 
public: 
    template <class T> 
    void Register (T *module) 
    { 
    m_modules [T::ModuleID] = module; 
    } 

    template <class T> 
    T *Module() 
    { 
    return reinterpret_cast <T *> (m_modules [T::ModuleID]); 
    } 
private: 
    std::map <int, void *> m_modules; 
}; 

class ModuleAInterface 
{ 
public: 
    static const unsigned ModuleID = 1; 
    virtual ~ModuleAInterface() {}; 
}; 

class ModuleBInterface 
{ 
public: 
    static const unsigned ModuleID = 2; 
    virtual ~ModuleBInterface() {}; 
    virtual void OutputString (char *string) = 0; 
}; 

class ModuleCInterface 
{ 
public: 
    static const unsigned ModuleID = 3; 
    virtual ~ModuleCInterface() {}; 
}; 

class ModuleA : public ModuleAInterface 
{ 
public: 
    ModuleA (Universe &my_app) : m_my_app (my_app) 
    { 
    m_my_app.Register (this); 
    } 
    void DoSomething() 
    { 
    ModuleBInterface *b = m_my_app.Module <ModuleBInterface>(); 
    b->OutputString ("Hello"); 
    } 
private: 
    Universe &m_my_app; 
}; 

class ModuleB : public ModuleBInterface 
{ 
public: 
    ModuleB (Universe &my_app) : m_my_app (my_app) 
    { 
    m_my_app.Register (this); 
    } 
private: 
    virtual void OutputString (char *string) { std::cout << string; } 
    Universe &m_my_app; 
}; 

class ModuleC : public ModuleCInterface 
{ 
public: 
    ModuleC (Universe &my_app) : m_my_app (my_app) 
    { 
    m_my_app.Register (this); 
    } 
private: 
    Universe &m_my_app; 
}; 

int main() 
{ 
    Universe my_app; 
    ModuleA a (my_app); 
    ModuleB b (my_app); 
    ModuleC c (my_app); 
    a.DoSomething(); 
} 

Une fois que le code a été divisé, seules les modifications aux interfaces causeront grande recompilation, en changeant la mise en œuvre des interfaces ne fera que recompiler le module modifié, rien d'autre devra être recompilé.

+0

C'est magnifique. Bien que sur la phase actuelle du projet, il y aurait assez de la variante # 2, je garderai à l'esprit cette solution au cas où le projet se développera. Je vous remercie. –

+0

En regardant le code, il y a quelques bits qui pourraient être améliorés. Se débarrasser du 'void *' pour un - remplacer par une interface de module commune (c'est-à-dire quelque chose dont toutes les interfaces de modules héritent) qui pourrait avoir des choses comme 'GetModuleName' et ainsi de suite. – Skizz

3

Je voudrais aller aveC# 2, et briser toutes les dépendances circulaires. Pourquoi A et C ont-ils tous deux besoin de se connaître les uns les autres? La dépendance peut-elle être factorisée dans un composant séparé?

+0

en effet: réseau ne devrait pas savoir sur le contrôle ou le stockage de données; cela rendrait également très difficile le test d'un seul module. – stijn

+0

Voici comment c'est fait maintenant. Merci, donc je m'en tiendrai là et j'essaierai de briser les dépendances. –

0

Pour autant que vous pouvez, toujours éviter les données globales, de sorte que la meilleure solution est probbly un mélange de 2) et 3):

En ce qui concerne les objets doivent avoir un lien vers un autre objet (et sont par ailleurs invalides Un tel objet devrait être passé avec le constructeur. Par conséquent votre version 2) est meilleure. Dans les cas où cela n'est pas possible (comme tous les objets se réfèrent), il peut être judicieux de créer une autre classe composite ModuleABC qui prend en charge la construction et les références de tous les objets. Pour éviter les cas de modules qui n'avez pas tous les liens nécessaires, leurs constructeurs devraient mieux être privé, et ModuleABC doivent être déclarés comme ami:

class ModuleABC: 

class ModuleA 
{ 
private: 
    ModuleA(); 
    friend ModuleABC 
... 
}; 
class ModuleB 
{ 
private: 
    ModuleB(); 
    friend ModuleABC 
... 
} 
class ModuleC 
{ 
private: 
    ModuleC(); 
    friend ModuleABC 
... 
} 

class ModuleABC 
{ 
public: 
    ModuleA a; 
    ModuleB b; 
    ModuleC c; 

    ModuleABC() 
    { 
     a.Connect(&b, &c); 
     b.Connect(&a, &c); 
     c.Connect(&a, &b); 
    } 
} 
0

Je vous conseille de regarder le modèle de conception de médiateur trouvé here. Il vous permet de définir un moyen de communiquer entre les classes/modules.

Questions connexes