GNT sans publicité, site mobile, fonctionnalitées exclusives...

Definition d'une interface (a la Java) a l'aide de templates

Le
Aurélien REGAT-BARREL
Bonjour à tous,
Je voudrais traduire une interface au sens pur, vraiment logique (ensemble
de méthodes). Je ne veux pas passer par l'héritage et les classes abstraites
de bases, car notamment je ne veux pas de polymorphisme. Je me suis donc
rabatu sur les templates.
Le but est de traduire un shéma UML où divers objets implémentent une
interface ITriplet qui stipule que :
- l'objet est construit à partir de 3 valeurs A, B et C (=> un triplet)
- l'objet possède une méthode GetValue(int) permettant d'accéder à ces 3
valeurs
- l'objet possède une méthode statique GetTripletName() qui retourne le nom
du triplet

Le truc étant que la plupart des objets font des modifications sur A, B et C
avant de les stocker. Les divers objets qui implémentent ITriplet n'ont rien
en commun et ne doivent pas être comparables, pas mélangeables, d'où pas
de polymorphisme.

Voilà les solutions auxquelles j'ai pensé, quel est votre opinion :

Pour l'exemple, un seul objet ABC inmplémente ITriplet.

1- on laisse le programmeur faire tout ce qu'il faut (on le guide au
maximum)

#define INTERFACE(x) /**/
#define IMPLEMENTS(x) /**/
#define WHERE(x) /**/

INTERFACE( ITriplet
{
public:
ITriplet( double A, double B, double C );
double GetValue( int Index ) const;
static std::string GetTripletName();
};
)

// triplet ABC
class ABC IMPLEMENTS( ITriplet )
{
public:
ABC( double A, double B, double C )
{
data.reserve( 3 );
data.push_back( A );
data.push_back( B );
data.push_back( C );
}
double GetValue( int Index ) const
{
return this->data.at( Index );
}
static std::string GetTripletName()
{
return "A.B.C";
}
private:
std::vector<double> data;
};

template <typename T>
WHERE( T : ITriplet )
T TripletBuilder()
{
return T( 0, 1, 2 );
}

int main()
{
ABC abc = TripletBuilder<ABC>();
}

2- on a bien une interface ITriplet sous forme de template. C'est plus
l'aspect macro qui est exploité ici

// functor pour construire le triplet
typedef std::vector<double> (TripletCtor)( double A, double B, double C );
// functor pour récupérer son nom
typedef std::string (TripletGetName)();

// interface ITriplet
template<TripletCtor Func, TripletGetName Name>
class ITriplet
{
public:
ITriplet( double A, double B, double C ) :
data( Func( A, B, C ) )
{
}
double GetValue( int Index ) const
{
return this->data.at( Index );
}
static std::string GetTripletName()
{
return Name();
}
private:
std::vector<double> data;
};

// triplet ABC
std::vector<double> ComputeABC( double A, double B, double C )
{
std::vector<double> result;
result.reserve( 3 );
result.push_back( A );
result.push_back( B );
result.push_back( C );
return result;
}
std::string GetNameABC()
{
return "A.B.C";
}

typedef ITriplet<ComputeABC, GetNameABC> ABC;

3 - fournir un template de vérification des méthodes attendues. Au lieu de
donner une interface au programmeur comme en 1, on donne un test à faire
passer à son objet.

template<typename T>
class ITripletImplementationChecker
{
public:
ITripletImplementationChecker()
{
double a = 0.0;
double b = 0.0;
double c = 0.0;
T t( a, b, c );
t.GetValue( 0 );
t.GetValue( 1 );
t.GetValue( 2 );
T::GetTripletName();
}
};

ITripletImplementationChecker<ABC> check_abc;


Comme j'ai l'impression de faire n'importe quoi, j'aimerais avoir votre
avis.
Merci de votre aide.

--
Aurélien REGAT-BARREL
Lire les 62 réponses

Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses Page 1 / 13
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Fabien LE LEZ
Le #785370
On Mon, 12 Jul 2004 11:30:44 +0200, "Aurélien REGAT-BARREL"

Je voudrais traduire une interface au sens pur, vraiment logique (ensemble
de méthodes). Je ne veux pas passer par l'héritage et les classes abstraites
de bases, car notamment je ne veux pas de polymorphisme.


Houlà... Donc, tu ne veux ni héritage, ni fonctions virtuelles, juste
t'assurer que :

- l'objet est construit à partir de 3 valeurs A, B et C (=> un triplet)
- l'objet possède une méthode GetValue(int) permettant d'accéder à ces 3
valeurs
- l'objet possède une méthode statique GetTripletName() qui retourne le nom
du triplet


C'est bien ça ?

Je pense que le mieux est de ne pas créer de code, mais juste mettre
dans la doc que la classe créée doit avoir ces fonctions.

Eventuellement, avec certaines fonctions de Boost (cherche du côté de
créer une macro "VERIFIER_APPARTENANCE_A_ITriplet(nom_classe)" qui
refuse la compilation si une des fonctions obligatoires est absente.

Aurélien REGAT-BARREL
Le #785369
Houlà... Donc, tu ne veux ni héritage, ni fonctions virtuelles, juste
t'assurer que :

- l'objet est construit à partir de 3 valeurs A, B et C (=> un triplet)
- l'objet possède une méthode GetValue(int) permettant d'accéder à ces 3
valeurs
- l'objet possède une méthode statique GetTripletName() qui retourne le
nom


du triplet


C'est bien ça ?


C'est tout à fait ça.

Je pense que le mieux est de ne pas créer de code, mais juste mettre
dans la doc que la classe créée doit avoir ces fonctions.


La doc se présente entre autre sous forme d'un diagramme des classes UML qui
définit cette interface. Il y aura bien un bout de texte qui va en parler,
mais je voudrais rendre mon code le plus parlant possible.

Eventuellement, avec certaines fonctions de Boost (cherche du côté de
créer une macro "VERIFIER_APPARTENANCE_A_ITriplet(nom_classe)" qui
refuse la compilation si une des fonctions obligatoires est absente.


C'était mon idée n° 3, fournir une fonction templétée de vérification.
Mais en fait y'a juste une chose qui me dérange : la moitié du code entre
ces classes est identique : accéder aux valeurs des triplets, accéder au
nom, accéder au nom de chaque triplet,... d'où mon idée n°2.
ITriplet n'est rien d'autre qu'un conteneur, seul le contenu varie entre les
différents triplets. C'est un peu comme si je voulais définir une classe
vector avec une fonction GetName() et un constructeur personnalisé...
Car en fin de compte seul le code du constructeur diffère ainsi que la
valeur des chaines renvoyées.
Je vais regarder boost. Merci.


--
Aurélien REGAT-BARREL


Fabien LE LEZ
Le #785367
On Mon, 12 Jul 2004 12:13:48 +0200, "Aurélien REGAT-BARREL"

Mais en fait y'a juste une chose qui me dérange : la moitié du code entre
ces classes est identique : accéder aux valeurs des triplets, accéder au
nom, accéder au nom de chaque triplet,... d'où mon idée n°2.
[...]
C'est un peu comme si je voulais définir une classe
vector avec une fonction GetName() et un constructeur personnalisé...


Ben oui, en C++ y'a un outil tout fait pour ça : l'héritage ;-)

Note que si tu peux utiliser l'héritage, mais sans qu'une classe soit
"indirectement convertible" en une autre, y'a un moyen :

template <char const* nom_classe> /* Pas sûr de moi, sur ce coup-là,
mais on doit pouvoir s'en sortir d'une façon ou d'une autre... */
class ITriplet
{
public:
ITriplet (double a, double b, double c)
{ data[0]= a; data[1]= b; data[2]= c; }
static std::string GetTripletName() { return nom_classe; }
double GetValue (int index) const
{
if (index>=0 && index<NB_VALEURS) return data[i];
throw "Erreur ITriplet" + GetTripletName() + "::GetValue";
}
protected:
enum { NB_VALEURS= 3 };
double data[NB_VALEURS]; /* Pas besoin d'un vector<> si on connaît
la taille à la compilation ;-) */
};

class MontripletMachin: public ITriplet <"MontripletMachin">
{
//...
};

Aurélien REGAT-BARREL
Le #785366
Ben oui, en C++ y'a un outil tout fait pour ça : l'héritage ;-)

Note que si tu peux utiliser l'héritage, mais sans qu'une classe soit
"indirectement convertible" en une autre, y'a un moyen :



C'est surtout le polymorphisme qui me gêne.

template <char const* nom_classe> /* Pas sûr de moi, sur ce coup-là,
mais on doit pouvoir s'en sortir d'une façon ou d'une autre... */
class ITriplet
{
public:
ITriplet (double a, double b, double c)
{ data[0]= a; data[1]= b; data[2]= c; }
static std::string GetTripletName() { return nom_classe; }
double GetValue (int index) const
{
if (index>=0 && index<NB_VALEURS) return data[i];
throw "Erreur ITriplet" + GetTripletName() + "::GetValue";
}
protected:
enum { NB_VALEURS= 3 };
double data[NB_VALEURS]; /* Pas besoin d'un vector<> si on connaît
la taille à la compilation ;-) */
};

class MontripletMachin: public ITriplet <"MontripletMachin">
{
//...
};


char * directement c'est pas possible, d'où le recours à un functor
GetTripletName() dans mon exemple 2.
Donc ta solution n'est pas possible. Sans compter qu'il faut aussi
customiser le constructeur, et encore d'autres trucs que j'ai pas mis
(GetValueName, ...).
Actuellement je penche pour une solution comme ceci :

// interface ITriplet
template<typename T>
class ITriplet
{
public:
ITriplet( double A, double B, double C ) :
data( T::Compute( A, B, C ) )
{
}
double GetValue( int Index ) const
{
return this->data.at( Index );
}
static std::string GetTripletName()
{
return T::GetName();
}
private:
std::vector<double> data;
};

// partie spécifique du triplet ABC
struct ABCImpl
{
static std::vector<double> Compute( double A, double B, double C )
{
std::vector<double> result;
result.reserve( 3 );
result.push_back( A );
result.push_back( B );
result.push_back( C );
return result;
}
static std::string GetName()
{
return "A.B.C";
}
};

// triplet ABC
typedef ITriplet<ABCImpl> ABC;

D'un côte le template qui définit l'interface (donc on est assez proche du
diagramme UML) + tout ce qui est redondant (stockage des triplets, accès aux
valeurs, ...) et de l'autre la partie spécifique à chaque triplet sous forme
d'une struct passée en paramètre du template ITriplet... C'est un peu le
principe du pattern façade, le template ITriplet jouant le rôle de...
l'interface.


--
Aurélien REGAT-BARREL

Fabien LE LEZ
Le #785364
Deuxième essai :

template <class T> class ITriplet
{
public:
double GetValue (int index) const
{
if (index<0 || index>=NB_VALEURS)
throw "Erreur ITriplet::GetValue";
return data[index];
}
protected:
enum { NB_VALEURS= 3 };
double data[NB_VALEURS];
ITriplet (double a, double b, double c)
{ data[0]= a; data[1]= b; data[2]= c; }
};

class Essai: public ITriplet<Essai>
{
public:
Essai (double a, double b, double c)
: ITriplet<Essai> (a,b,c) {}
static char const * GetName() { return "Essai"; }
};

class Essai2: public ITriplet<Essai>
{
public:
Essai2 (double a, double b, double c)
: ITriplet<Essai> (a,b,c) {}
static char const * GetName() { return "Essai2"; }
};

Essai et Essai2 n'ont aucun parent commun.
De plus, il n'y a pas de polymorphisme et pas de virtualité.
Publicité
Suivre les réponses
Poster une réponse
Anonyme