2010-03-25 4 views
3

Je travaille sur un framework de plugin utilisant des bibliothèques partagées dynamiques chargées basées sur le modèle de point d'extension d'Eclipse (et probablement d'autres). Tous les plugins partagent des propriétés similaires (nom, identifiant, version, etc.) et chaque plugin pourrait en théorie satisfaire n'importe quel point d'extension. La gestion du plugin (ie Dll) est gérée par une autre bibliothèque, tout ce que je fais c'est gérer des collections d'interfaces pour l'application.éviter d'être en tant qu'identificateurs d'interface C++ OOP

J'ai commencé par utiliser un enum PluginType pour distinguer les différentes interfaces, mais j'ai rapidement réalisé que l'utilisation de fonctions de template rendait le code beaucoup plus propre et laissait le travail au compilateur, plutôt que de me forcer à utiliser déclarations.

Le seul problème est lorsque j'ai besoin de spécifier une fonctionnalité similaire pour les membres de la classe - l'exemple le plus évident est le plugin par défaut qui fournit une interface particulière. Une classe Settings gère tous les paramètres, y compris le plugin par défaut pour une interface.

-à-dire Skin newSkin = settings.GetDefault<ISkin>();

Comment stocker la valeur par défaut ISkin dans un conteneur sans avoir recours à d'autres moyens d'identification de l'interface?

Comme je l'ai mentionné ci-dessus, j'utilise actuellement membre std::map<PluginType, IPlugin> Settings::defaults pour y parvenir (où IPlugin est une classe de base abstraite que tous les plug-ins dérivent. Je peux alors dynamic_cast à l'interface désirée lorsque cela est nécessaire, mais cela sent vraiment de mauvaise conception pour moi et introduit plus de mal que de bien, je pense que

accueillerait favorablement des conseils

modifier. voici un exemple de l'utilisation actuelle des plug-ins par défaut

typedef boost::shared_ptr<ISkin> Skin; 
typedef boost::shared_ptr<IPlugin> Plugin; 
enum PluginType 
{ 
    skin, 
    ..., 
    ... 
} 

class Settings 
{ 
public: 
    void SetDefault(const PluginType type, boost::shared_ptr<IPlugin> plugin) { 
    m_default[type] = plugin; 
    } 
    boost::shared_ptr<IPlugin> GetDefault(const PluginType type) { 
    return m_default[type]; 
    } 
private: 
    std::map<PluginType, boost::shared_ptr<IPlugin> m_default; 
}; 

SkinManager::Initialize() 
{ 
    Plugin thedefault = g_settings.GetDefault(skinplugin); 
    Skin defaultskin = boost::dynamic_pointer_cast<ISkin>(theskin); 
    defaultskin->Initialize(); 
} 

J'appellerais plutôt getdefault comme suit, avec un cast automatique vers la classe dérivée. Cependant, je dois me spécialiser pour chaque type de classe.

template<> 
Skin Settings::GetDefault<ISkin>() 
{ 
    return boost::dynamic_pointer_cast<ISkin>(m_default(skin)); 
} 
+0

Pouvez-vous poster un exemple de code de la façon dont on pourrait utiliser les valeurs par défaut? J'ai du mal à imaginer ce dont tu parles. –

+0

Si 'IPlugin' est une classe abstraite, vous devrez stocker des pointeurs dans votre map (vous aurez besoin de le faire de toute façon, sinon vous aurez des problèmes de tranchage). –

+0

salut Kristo, ont ajouté plus d'un exemple – AlasdairC

Répondre

0

Quel est le problème de l'énumération? Le manque d'extensibilité.

Comment avoir de l'extensibilité tout en conservant son identification? Vous avez besoin d'un objet complet, de préférence avec un type spécifique.

Fondamentalement, vous pouvez vous en sortir avec:

class IPluginId 
{ 
public: 
    virtual IPluginId* clone() const = 0; 
    virtual ~IPluginId(); 

    bool operator<(const IPluginId& rhs) const { return mId < rhs.mId; } 
    bool operator==(const IPluginId& rhs) const { return mId == rhs.mId; } 

protected: 
    static size_t IdCount = 0; 
    IPluginId(size_t id): mId(id) {} 
private: 
    size_t mId; 
}; 

template <class Plugin> 
class PluginId 
{ 
public: 
    PluginId(): IPluginId(GetId()) {} 
    IPluginId* clone() const { return new PluginId(*this); } 
private: 
    static size_t GetId() { static size_t MId = ++IdCount; return MId; } 
}; 

Maintenant, comme pour l'utilisation, il obtiendrait:

// skin.h 

class ISkin; 

struct SkinId: PluginId<ISkin> {}; // Types can be forward declared 
            // Typedef cannot 

class ISkin: public IPlugin { /**/ }; 

Et vous pouvez maintenant obtenir l'utilisation:

class Settings 
{ 
public: 
    template <class Plugin> 
    void SetDefault(boost::shared_ptr<Plugin> p); 

    template <class Plugin> 
    boost::shared_ptr<Plugin> GetDefault(const PluginId<Plugin>& id); 

private: 
    boost::shared_ptr<IPlugin> GetDefault(const IPluginId& id); 
}; 

Le La version du modèle est implémentée en terme de non-modèle et effectue automatiquement la descente. Il est impossible que le pointeur soit du mauvais type parce que le compilateur fait le contrôle du type, donc vous partez avec un static_cast :)

Je sais que le downcasting est un peu moche, mais ici vous venez de down_cast une méthode GetDefault et son type est vérifié au moment de la compilation.

encore plus facile (Produisons les clés à la volée):

class Settings 
{ 
public: 
    template <class Plugin> 
    void SetDefault(const boost::shared_ptr<Plugin>& p) 
    { 
    mPlugins[typeid(Plugin).name()] = p; 
    } 

    template <class Plugin> 
    boost::shared_ptr<Plugin> GetDefault() const 
    { 
    plugins_type::const_iterator it = mPlugins.find(typeid(Plugin).name()); 
    if (it == mPlugins.end()) return boost::shared_ptr<Plugin>(); 

    return shared_static_cast<Plugin>(it->second); 
    } 

private: 
    typedef std::map<std::string, std::shared_ptr<IPlugin> > plugins_type; 
    plugins_type mPlugins; 
}; 

Cependant, il est moins sûr que la première alternative, notamment, vous pouvez mettre quelque chose là aussi longtemps qu'il hérite de IPlugin, de sorte que vous pouvez mettre MySkin par exemple, et vous ne serez pas en mesure de le récupérer via ISkin parce que typeid(T).name() résoudra à un nom différent.

+0

Bonjour Matthieu, merci pour la réponse. En commençant par ce dernier, j'aime vraiment votre idée d'utiliser la propriété 'type_info.name', cela résout complètement mon problème en une fois. En fait, je regardais la typographie et les choses semblables aujourd'hui et je n'ai pas réussi à faire le saut de la foi pour réaliser que je pouvais utiliser cela comme une propriété de différentes manières. Cependant, je ne suis pas complètement votre première suggestion, si vous êtes toujours désireux [:)] pourriez-vous fournir l'implémentation pour les deux méthodes GetDefault? Je préférerais certainement faire cela en static_cast si possible – AlasdairC

+0

Content d'être utile :) Vous pourriez peut-être utiliser un 'multimap' si vous voulez avoir plusieurs types de plugins pour une valeur d'identifiant commune. –

+0

Je ne sais pas si c'est possible, mais il y a des cas où j'aimerais obtenir le type 'SkinId' d'un' ISkin' (par exemple). Est-il possible d'éviter de passer le 'IPluginId' comme paramètre à la méthode' GetDefault'? c'est-à-dire que le type est résolu via le paramètre 'T' de la déclaration de type retour? Au moment je dois passer l'id comme le coup de pouce 'suivant :: shared_ptr peau = settings.GetDefault (SkinID());' – AlasdairC

0

Downcasting peut être évité en utilisant le Visitor-Pattern, mais cela peut nécessiter refactoring substantielle de l'architecture vous. De cette façon, vous n'avez pas à manipuler les plugins différemment. Créer des instances de plugins peut être fait en utilisant un Factory. J'espère que cela vous donne un point de départ. Si vous souhaitez plus de détails, vous devez fournir plus d'informations sur votre architecture.

+0

Salut, Il semble que le modèle de visiteur aurait pu être un bien meilleur choix étant donné que chaque plugin pourrait en théorie fournir l'une des interfaces. Je suis un peu déchiré cependant, car un refactoring majeur serait nécessaire pour y parvenir. – AlasdairC

1

Vous pouvez utiliser un conteneur de séquence de boost :: variante au lieu (code d'illustration non testé):

tyepdef boost::variant< 
boost::shared_ptr<ISkin>, 
boost::shared_ptr<IPluginType2>, 
boost::shared_ptr<IPluginType3>, 
etc...> default_t; 
std::deque<default_t> defaults; 

Puis:

template <class T> 
boost::shared_ptr<T> GetDefault() { 
    for(std::deque<default_t>::iterator it = defaults.begin(), endIt = defaults.end(); 
     it != endIt; 
     ++it) { 
     boost::shared_ptr<T>* result = boost::get< boost::shared_ptr<T> >(*it); 
     if(result) { 
      return *result; 
     } 
    } 
    return boost::shared_ptr<T>(0); 
} 
+0

Cela ressemble à une bonne solution, merci pour la suggestion – AlasdairC

0

Je suis sûr que vous pouvez faire quelque chose comme ça.

class Settings 
{ 
    public: 
     // ... 
     template <class T> 
     boost::shared_ptr<T> GetDefault() 
     { 
      // do something to convert T to an object (1) 
      return m_default[T_as_an_obj]; 
     } 
     // .... 
}; 

SkinManager::Initialize() 
{ 
    boost::shared_ptr<ISkin> defaultskin = g_settings.GetDefault<ISkin>(); 
    defaultskin->Initialize(); 
}   

ligne (1) est la partie que je pense que je l'ai vu faire avant, mais ne savent pas comment faire moi-même. Notez également que l'implémentation en cours renvoie un pointeur null si vous transmettez un type que la classe Settings n'a pas encore vu. Vous devrez en tenir compte d'une manière ou d'une autre.

+0

Hi Kristo, ouais je vérifie toujours le pointeur après l'appel pour être sûr. La ligne 1, comme vous le dites, est la partie la plus difficile, sans avoir recours aux instructions switch, typeid (T) et autres. – AlasdairC