Bonjour,
Dans le cadre d'un petit projet contenant notamment des clases de widgets affichables à l'écran, je cherche un moyen élégant de fournir une liste de paramètres à mes classes de widget (par exemple un bouton, un label de texte, ...).
L'idée est qu'un widget peut prendre un nombre important de paramètres, tous optionnels, et qu'on veut pouvoir spécifier dans un ordre quelconque. Or dans ce cadre un constructeur 'classique' est inapproprié:
1 2 3 4
|
class Button {
Button(Coord pos, int width, int height, Color c, float alpha a, SizePolicy policy, float textSize, Color focusColor, Color selectedColor, (void)(*fn) clickedCallback, /* etc... */);
}; |
En effet, la plupart du temps l'immense majorité des propriétés utilisées sont celles par dafaut.
D'où l'idée d'avoir un constructeur capable de recevoir une liste de propriétés et qui ira piocher dans ses propriétés par défaut pour celles non spécifiées, le tout avec une syntaxte la plus légère possible. Quelque chose du genre:
1 2 3 4 5 6
|
class Button {
Buton(ParamSet& params) {
_color = params.getOrDefault<ColorParam>( /*defaultValue*/ Color::WHITE);
}
}; |
L'instanciation se faisant avec quelque chose comme:
btn = new Button( params<<ColorParam(Color::BLUE)<<PosParam(12,34) );
J'ai donc commencé une petite classe abstraite générique qui permet d'avoir un type de base unique (pour être ensuite stocké dans une collection):
Puis une classe dérivée, template, capable de contenir un paramètre 'simple' avec son type associé:
1 2 3 4 5 6 7 8 9
|
template <typename PARAMTYPE>
class SingleParam : public IParam {
public:
SingleParam(PARAMTYPE p) : value(p) {}
virtual PARAMTYPE get() { return value; }
PARAMTYPE value;
typedef PARAMTYPE ParamValueType;
}; |
(on notera la déclaration du typedef pour lier le type de la valeur sotckée par ce paramètre en fonction de la classe de paramètre)
Et une classe concrètement utilisable un 'AlphaParam' qui stocke ici un float:
1 2 3 4 5
|
class AlphaParam : public SingleParam<float> {
public:
AlphaParam(float a) : SingleParam(a) {}
}; |
On continue avec la création d'une classe abstraite pour contenir une liste de paramètres:
1 2 3 4 5
|
class IParamSet {
public:
virtual ~IParamSet() {}
}; |
Et sa classe dérivée basée sur des méthode getter/setter templates:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
class ParamSet : public IParamSet{
public:
virtual ~ParamSet() { /* ... */ }
template <typename PARAMCLASS> typename PARAMCLASS::ParamValueType get() {
for (list<IParam*>::iterator it = params.begin() ; it != params.end() ; ++it) {
IParam* iparam = *(it);
PARAMCLASS* param = dynamic_cast<PARAMCLASS*>(iparam);
if (param) {
return param->get();
}
}
throw Exception("param not found.");
}
template <typename PARAMCLASS> void add(typename PARAMCLASS::ParamValueType paramvalue) {
IParam* p = static_cast<IParam*>(new PARAMCLASS(paramvalue));
params.push_back(p);
}
list<IParam*> params;
}; |
A ce niveau, tout va bien puisque l'utilisateur de ces classes peut s'en servir de la façon suivante:
1 2 3 4 5 6 7
|
// création et population d'un ParamSet
ParamSet paramset;
paramset.add<AlphaParam>(0.5f );
// récupération d'une propriété d'un paramSet
float alpha = paramset.get<AlphaParam>(); |
Jusque là tout va bien.
Mais ma classe IParam doit pouvoir prendre plusieurs formes car le projet a vocation à propsoer un binding Javascript des classes widget. Et donc proposer en plus d'une API 'sympa' pour les codeurs C++, une API 'tout aussi sympa' pour ceux qui coderont en Javascript.
Dans le cas d'une instanciation depuis le Javascript, le binding recevra un unique paramètre de type 'objet JS', soit l'équivalent d'un JSON. Exemple:
1 2 3 4 5 6 7
|
myJsButton = new JsButton( {
pos:[100,200],
color: [255,255,255],
alpha: 0.5,
onClickedFn: myOtherFunction
} ); |
L'idée est donc d'avoir une seconde class dérivée de IParam dont l'implémentation dérivant de IParam capable d'extraire les paramètres à partir de l'objet JSON venant du monde Javascript:
1 2 3 4 5 6 7 8
|
class JSParamSet {
JSParamSet(JSObject o) : data(o) { /* ... */ }
template <typename PARAMCLASS> typename PARAMCLASS::ParamValueType get() {
return JSObjToNativeParam<PARAMCLASS>(data);
}
JSObject data;
}; |
(.. avec une fonction JSObjToNativeParam qui se charge d'extraire le pramètre de l'instance de de JSObject via une spécialisation de template).
D'où une utilisation dans ma classe Button à partir du type abstrait IParam
1 2
|
class Button(IParam& params) { _alpha = params.get<AlphaParam>(); } |
Problème: il m'est évidemment impossible de dériver des méthodes templates d'une classe de base commune. Donc impossible de faire une sorte de classe IParam qui proposerait à ma classe Button des méthodes template comme 'get<paramtype>' ou 'getOrDefault<paramtype>' destinées à être surchargées dans des implémentations visant des utilisations distinctes (natif, js, ...).
Une idée pour m'en sortir ?
A noter que je suis assez ouvert à toute proposition, même peut optimum en termes de performances (on n'instancie pas 100 000 boutons par seconde). Le principal étant que:
- le codeur C++ ait un moyen de déclarer ses paramètres directement dans l'appel au constructeur de la classe widget, avec la syntaxte la plus concise possible.
- le code JS ait une API qui se base sur le passage en paramètre d'un unique objet JS (JSON like) pour faire la même chose.
Merci d'avance
Partager