Envoyé par
Pyramidev
Dans le contexte :
Je ne vois pas trop où tu veux en venir avec les classes de politique.
En fait, l'idée de base est simple : on respecte le principe de la responsabilité unique presque jusqu'à l'absurde, en faisant en sorte de pouvoir disposer d'objets qui n'exposent qu'un ensemble très restreint de fonctionnalités clairement établie; en faisant en sorte d'obtenir un trait de politique par possibilité envisagée dans un point de variation.
Mais pour te faire plus facilement comprendre l'idée, je vais te proposer un cas d'étude. Mettons que l'on veuille mettre en place un système de service locator le plus évolutif possible. L'analyse des besoins a déterminé qu'il y avait au moins trois grands points de variation, à savoir que le localisateur peut:- accepter au choix un ou plusieurs service(s) à localiser
- accepter au choix de localiser des services polymorphes ou non
- accepter l'enregistrement automatique d'un nouveau service "à la demande" (pour autant qu'il soit possible de créer ce service sans avoir à lui fournir de paramètre)
Chacune de ces possibilités est exclusive par rapport à son homologue: soit le service est polymorphe, soit il ne l'est pas; soit on n'accepte qu'un et un seul service, soit on en accepte plusieurs, etc, mais on peut envisager toutes les combinaisons raisonnables utilisant les trois points de variation.
Pour mettre en place le point de variation concernant l'arité des éléments maintenus par le service locator, nous allons sans doute créer deux traits de politique distincts à savoir quelque chose comme
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
|
template <typename BASETYPE>
class UniqueHolderTrait{
/* quelques alias de type, utilisés par facilité */
using value_type = BASETYPE;
using ptr_type = std::unique_ptr<value_type>;
public:
/* on peut enregistrer un nouvel élément, qui peut nécessiter des paramètres */
template <typename REALTYPE, typename ... Args>;
void registerItem(Args ... args){
static_assert(std::is_base_of<value_type, REALTYPE>::value, "Bad type requested");
ptr_.reset(new REALTYPE{args...});
index_=typeid(REALTYPE);
}
/* on peut récupérer l'élément sous-jacent sous forme de référence ou sous forme de référence constante */
template <typename REALTYPE>
REALTYPE & get(){
assert(typeid(REALTYPE) == index_);
return static_cast<REALTYPE &>(*(ptr_.get());
}
template <typename REALTYPE>
REALTYPE const & getConst(){
assert(typeid(REALTYPE) == index_);
return static_cast<REALTYPE const &>(*(ptr_.get());
}
template <typename REALTYPE>
bool exists() const{
return index_==typeid(REALTYPE) && ptr_.get()!= nullptr;
}
private:
ptr_type ptr_;
std::type_index index_;
};
/* pour maintenir des éléments multiples */
template <typename BASETYPE>
class MultipleHolderTrait{
using value_type = BASETYPE;
using ptr_type = std::unique_ptr<value_type>;
using map_type = std::map<std::type_index, ptr_type;
public:
template <typename REALTYPE, typename ... Args>;
void registerItem(Args ... args){
static_assert(std::is_base_of<value_type, REALTYPE>::value, "Bad type requested");
intems[typeid(REALTYPE)].reset(new REALTYPE{args...});
}
template <typename REALTYPE>
REALTYPE & get(){
assert(items_.find(typeid(REALTYPE) != items_.end() && "Bad type requested");
return static_cast<REALTYPE &>(*(items_[typeid(REALTYPE)].get());
}
template <typename REALTYPE>
REALTYPE const & getConst(){
assert(items_.find(typeid(REALTYPE) != items_.end() && "Bad type requested");
return static_cast<REALTYPE &>(*(items_[typeid(REALTYPE)].get());
}
template <typename REALTYPE>
bool exists() const{
auto found = items_.find(typeid(REALTYPE);
return found != items_.end() && found->second.get()!= nullptr;
}
private:
map_type items_;
}; |
Tu remarqueras que l'interface de ces deux classes est totalement identique. Il n'y a vraiment guère que leur comportement interne qui change
Nous pourrons dés lors créer une politique d'arité qui choisira l'un de ces traits en fonction d'un tag proche de
1 2 3 4 5 6 7 8 9 10 11 12
| struct UniqueItemTag{};
struct MultipleItemTag{};
template <typename BASETYPE, typename ARITYTAG = UniqueItemTag>
class ArityPolicy : public UniqueHolderTrait<BASETYPE>{
};
/* et une spécialisation partielle pour les éléments multiples: */
template <typename BASETYPE>
class ArityPolicy<BASETYPE, MultipleItemTag>: public MultipleHolderTrait<BASETYPE>{
}; |
Grace à cela, tu pourras aussi bien écrire un code proche de
using MyUniqueHolder = ArityPolicy<MonTypeDeBase>;
qui créeras un alias de type nommé MyUniqueHolder sur un élément qui ne peut contenir qu'un seul élément à la fois ou un code proche de
using MyMultipleHolder= ArityPolicy<MonTypeDeBase, MultipleItemTag>;
qui créera un alias de type sur un élément qui peut contenir plusieurs éléments à la fois.
Et nous ferons pareil (en utilisant les fonctionnalités par le fichier type_traits) pour les deux autres caractéristiques, en créant pour la première des traits et une politique proches de:
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
|
template <typename BASETYPE>
struct PolymorphicTrait{
template <typename REALTYPE>
using final_type = REALTYPE;
};
template <typename BASETYPE
struct NonPolymorphicTrait{
/* il y aura tout un système pour permettre le type erasure ici */
template <typname REALTYPE>
using final_type = Derived<REALTYPE>;
};
/* par défaut, la politique considère que les éléments sont polymorphes */
template <typename BASETYPE, bool poly=true>
class PolymorphicPolicy : {
public:
template <typename REALTYPE>
using final_type = PolymorphicTrait<BASETYPE>::final_type<REALTYPE>;
};
template <typename BASETYPE>
class PolymorphicPolicy<BASETYPE, !std::is_polymorhpic<BASETYPE>::value>{
public:
template <typename REALTYPE>
using final_type = NonPolymorphicTrait<BASETYPE>::final_type<REALTYPE>;
}; |
et, pour la deuxième, nous créerons des traits de politiques et une politique qui ressemblerait à quelque chose comme
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
|
template <typename BASETYPE>
struct AutoregistryAllowed{
template <typename REALTYPE, typename ARITYTAG >
REALTYPE & get(ArityPolicy<BASETYPE, ARITYTAG > & holder){
if(! holder.template exists<REALTYPE>())
holder.template register< PolymorphismPolicy<BASETYPE>::final_type<REALTYPE>>();
return holder.template get<REALTYPE>();
}
template <typename REALTYPE, typename ARITYTAG >
REALTYPE & getConst(ArityPolicy<BASETYPE, ARITYTAG > & holder){
if(! holder.template exists<REALTYPE>())
holder.template register< PolymorphismPolicy<BASETYPE>::final_type<REALTYPE>>();
return holder.template getConst<REALTYPE>();
}
};
template <typename BASETYPE>
struct AutoregistryNotAllowed{
template <typename REALTYPE, typename ARITYTAG >
REALTYPE & get(ArityPolicy<BASETYPE, ARITYTAG > & holder){
return holder.template get<REALTYPE>();
}
template <typename REALTYPE, typename ARITYTAG >
REALTYPE & getConst(ArityPolicy<BASETYPE, ARITYTAG > & holder){
return holder.template getConst<REALTYPE>();
}
};
/* il manque plus que la politique, qui choisira entre ces deux traits en fonction
* de l'aspect défaut constructible des éléments.
*
* Je ne la met pas ici, mais elle serait nommé AutoRegistrationPolicy
*/ |
Une fois que tout cela sera fait, nous pourrons utiliser nos trois politiques pour définir notre service locator sous une forme qui serait dés lors proche de
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| /* Par défaut, il n'autorise qu'un élément */
template <typename BASETYPE, typename ARITYTAG= UniqueItemTag>
class ServiceLocator{
using arity_policy = ArityPolicy<BASETYPE, TAG>; // choisi automatiquement le bon trait en fonction de ARITYTAG
using polymorphic_policy = PolymorphismPolicy<BASETYPE>; // choisi automatiquement le bon trait en fonction de l'aspect polymorphe de BASETYPE
using autoreg_policy = AutoRegistrationPolicy<BASETYPE>; // choisi automatiquement le bon trait en fonction de l'aspect "défaut constructible" de BASETYPE
public:
template <typename REALTYPE, typename ... Args>;
void registerItem(Args ... args){
holder.template register< PolymorphismPolicy<BASETYPE>::final_type<REALTYPE>>();
}
template <typename REALTYPE>
REALTYPE & get(){
return autoreg_policy.template get<REALTYPE>(holder_);
}
template <typename REALTYPE>
REALTYPE const & get(){
return autoreg_policy.template getConst<REALTYPE>(holder_);
}
private:
static arity_policy holder_;
}; |
NOTE: Pour une fois, j'ai écrit ce code de tete, sans le tester d'aucune manière (de toutes manières, le principe est plus important que l'implémentation réelle du bastringue). Il n'est donc pas garanti qu'il puisse compiler "en l'état", d'autant plus qu'il manque des pans entiers de l'implémentation
A part ça, je précise aussi qu'il s'agit de code en entreprise. Or, dans l'entreprise où je travaille, si je vais trop loin dans la programmation par templates, les autres développeurs vont m'accuser d'écrire du code trop compliqué donc impossible à maintenir. Pour eux, les classes de politique, cela va déjà trop loin. Du coup, quand j'écris des templates, c'est avec parcimonie.
A la maison, par contre, je peux faire ce que je veux.
Hé bien, ca, c'est bien moche .
D'autant plus que la technique pousse, en quelques sortes, le SRP dans ses derniers retranchements, et qu'elle permet donc, pour chaque trait de politique, de ne s'inquiéter que de ce qui importe vraiment à un moment donné
Partager