2010-04-04 5 views
3

Je n'ai pas lu le livre Modern C++ Design mais j'ai trouvé l'idée d'injection de comportement à travers des modèles intéressante. J'essaie maintenant de l'appliquer moi-même.Application d'une question de conception basée sur des règles

J'ai une classe qui a un enregistreur que je pensais pouvoir être injecté comme politique. L'enregistreur a une méthode log() qui prend un std :: string ou std :: wstring en fonction de sa politique:

// basic_logger.hpp 
template<class String> 
class basic_logger 
{ 
public: 
    typedef String string_type; 

    void log(const string_type & s) { ... } 
}; 
typedef basic_logger<std::string> logger; 
typedef basic_logger<std::wstring> wlogger; 

// reader.hpp 
template<class Logger = logger> 
class reader 
{ 
public: 
    typedef Logger logger_type; 

    void read() 
    { 
     _logger.log("Reading..."); 
    } 

private: 
    logger_type _logger; 
}; 

Maintenant, le est des quêtes, devrait prendre le lecteur un enregistreur comme argument, comme ci-dessus, ou devrait-il prendre une chaîne, puis instancier un basic_logger en tant que variable d'instance? Comme ceci:

template<class String> 
class reader 
{ 
public: 
    typedef String string_type; 
    typedef basic_logger<string_type> logger_type; 

    // ... 

private: 
    logger_type _logger; 
}; 

Quelle est la procédure à suivre?

+1

S'il vous plaît, moins de choses d'injection - c'est C++. –

+0

Je sais que je suis un peu endommagé par Java (je travaille principalement avec lui mais je n'en suis pas un grand fan). Le comportement et l'état doivent toujours être injectés (ou ce que vous voulez appeler) d'une manière ou d'une autre, que ce soit en C++, Ruby ou autre. Avec C++, d'après ce que je comprends de Boost, la conception basée sur les politiques est la voie à suivre. J'essaie juste d'envelopper ma tête autour d'elle. – Arthur

+1

En fait lire Modern C++ Design vous aiderait probablement le mieux, car Alexandrescu montre des exemples similaires à cela. –

Répondre

2

Pour utiliser réellement une classe de stratégie, la stratégie doit être un paramètre de modèle. Un exemple est le paramètre char_traits de basic_string, même si cela est implémenté différemment des politiques de MC++ D, qui utilisent l'héritage pour utiliser l'optimisation de classe de base vide et pour faciliter l'ajout à l'interface publique d'une classe (bien mieux que envelopper chaque méthode possible, encore une fois, lire MC++ D). Vous pouvez toujours fournir une valeur par défaut:

template<class String, class Logger=basic_logger<String> > 
struct reader : Logger { 
    void read() { 
    this->log("Reading..."); 
    } 
}; 
+0

Je suppose que vous avez raison de dire que je devrais lire le livre d'Alexandrescu. J'ai trouvé quelques exemples du livre, mais il est un peu difficile de mettre les exemples en contexte sans le livre. Je ne connaissais pas l'optimisation de la classe de base vide par exemple. Quoi qu'il en soit, je suppose que cela répond à ma question. Merci! – Arthur

1

La question doit-elle être paramétrée sur le type de son enregistreur ou sur le type de chose qu'il lit? Si c'est la question, j'aurais pensé que la réponse était évidente - la dernière.

Le problème avec cette question IMHO est que ni String ni Logger sont en fait des politiques. Une politique dit à la compilation, comment quelque chose comme un logger devrait être utilisé pour la journalisation - votre code fournit simplement un type de logger au lecteur, ce qui pourrait aussi bien avoir été fait à l'exécution, en utilisant l'héritage.

+0

Vous avez raison. L'enregistreur, dans ce cas, est préférable d'être défini au moment de l'exécution. Mais si vous voyez l'enregistreur comme une politique de journalisation, je pense que Roger a répondu à ma question ci-dessus. Comme je l'ai déjà dit, j'essaie toujours de comprendre les politiques ... – Arthur

1

Les stratégies sont normalement des paramètres qui affectent le comportement de la classe.

Il est effectivement assez difficile d'extraire les politiques d'une classe, et le plus difficile est que les politiques doivent couvrir les concepts orthogonaux afin que vous puissiez en changer un sans affecter les autres ... ce qui est assez difficile comme vous pouvez l'imaginer .

Si vous voulez voir un bon exemple d'utilisation de la politique, recherchez Loki::Singleton qui est entièrement démontré dans le livre.

template 
< 
    typename T, 
    template <class> class CreationPolicy = CreateUsingNew, 
    template <class> class LifetimePolicy = DefaultLifetime, 
    template <class, class> class ThreadingModel = LOKI_DEFAULT_THREADING_NO_OBJ_LEVEL, 
    class MutexPolicy = LOKI_DEFAULT_MUTEX 
> 
class SingletonHolder; 

Impressionnant, n'est-ce pas? Le principe de la conception basée sur des règles est que vous essayez de décomposer les différentes actions de votre classe afin de pouvoir les raisonner de manière indépendante.

Eh bien, maintenant, je dois admettre que je ne suis pas tout à fait à l'aise avec l'idée d'exiger la classe avec un certain nombre de paramètres du modèle, personnellement, j'aurais préféré quelque chose le long:

template 
< 
    class T, 
    class CreationPolicy = CreateUsingNew<T>, 
    class LifetimePolicy = DefaultLifeTime<T>, 
    class MutexPolicy = LOKI_DEFAULT_MUTEX, 
    template <class, class> class ThreadingModel = LOKI_DEFAULT_THREADING_NO_OBJ_LEVEL 
> 
class SingletonHolder; 

Le dernier ne peut pas vraiment être aidé , vous êtes censé passer la classe SingletonHolder elle-même.

Cependant je trouve plus facile d'échanger des politiques ici, il me permet de définir des politiques comme:

template <class T, size_t Param> MyCreationPolicy; 

Et utiliser directement, sans avoir à envelopper pour une valeur donnée de param afin qu'elle corresponde à la signature.

+0

Merci pour la clarification! Très utile! – Arthur