II. Règles de transformation d'un modèle objet en C▲
II-A. Objectif▲
Pour modéliser des programmes complexes, la méthodologie objet s'impose aujourd'hui. Cependant, pour des raisons techniques, il est parfois nécessaire de développer en C.
De prime abord, le C n'est pas un langage adapté à une modélisation objet. Mais cela est possible si nous nous équipons de règles de transformation d'un modèle objet en C afin d'avoir une programmation des différents objets homogènes et cohérents.
II-B. Les classes▲
II-B-1. Propriétés▲
Les propriétés d'une classe sont un ensemble d'attributs caractérisant un objet (ex. : la couleur d'un stylo : couleur est un attribut de l'objet stylo, il est alors aisé d'instancier un stylo bleu ou vert). Nous considérerons que les propriétés sont privées (Accessible uniquement avec des accesseurs). En C, une propriété est un composant d'une structure.
Le nom des propriétés respecte la règle des noms de variables.
Les propriétés doivent être décrites après les méthodes.
II-B-2. Méthodes privées, protégées et publiques▲
Les méthodes sont un ensemble de fonctions ou de procédures agissant sur un objet. Elles correspondent aux méthodes décrites dans le modèle (ex. : la méthode écrire pour un stylo). En C, nous ne pouvons pas faire la distinction entre une méthode publique, privée ou protégée. Nous considérerons que les méthodes sont publiques. Les méthodes se matérialiseront par des pointeurs sur des fonctions définies en interne dans un module.
Le premier paramètre d'une méthode est un pointeur sur un objet de type de la classe contenant la méthode (elle est nommée « this »). Cette pratique permet d'accéder aux propriétés de la classe.
Les méthodes doivent être décrites avant les propriétés.
Chaque méthode a une fonction associée. Une fonction associée a une méthode est nommée avec le nom de la méthode infixée par le nom de la classe.
Exemple : Soit la méthode Ecrire de la classe Stylo, la fonction associée à la méthode se nommera : Ecrire_stylo.
II-B-3. Méthodes obligatoires▲
II-B-3-a. Le(s) constructeur(s)▲
Cette méthode permet d'instancier proprement une classe. Elle alloue une nouvelle instance pour laquelle elle initialise les propriétés de la classe et positionne les méthodes sur les bonnes fonctions. Le constructeur se nomme « Instancier_<Nom de la classe> » (équivalent d'un new) Cette méthode n'est pas une méthode de l'objet, elle est une fonction d'accès au fichier source C. Le constructeur doit obligatoirement comporter les phases :
- allocation d'un objet (et teste le bon fonctionnement de l'allocation) ;
- affectation des méthodes ;
- initialisation des propriétés ;
- instanciation des agrégations ou des classes mères ;
- retour du nouvel objet alloué.
REMARQUE : Il n'est pas interdit d'avoir plusieurs constructeurs. Dans ce cas, il faut préciser la nature du constructeur : « Instancier_<Nature>_<Nom de la classe> ». Un constructeur peut éventuellement faire appel à un autre constructeur de la même classe (Exemple : Instancier_copie_stylo).
II-B-3-b. Le destructeur▲
Cette méthode permet de libérer proprement une instance. Elle libère l'ensemble des objets instanciés dans sa classe et la mémoire allouée lors du constructeur. Elle initialise le pointeur sur l'instance à NULL. Le destructeur est une méthode et se nomme « Liberer » (équivalent d'un delete).
Cette méthode est une méthode de l'objet. La particularité de cette méthode est que le premier paramètre est un pointeur sur un pointeur d'instance.
Le destructeur doit obligatoirement respecter les phases :
- suppression des associations liées à l'objet instancié ;
- libération des objets agrégations instanciés dans la classe ;
- libération de l'instance ;
- initialisation de l'instance à NULL.
REMARQUE : il ne peut y avoir qu'un seul destructeur par classe.
II-B-3-c. Les accesseurs▲
Les propriétés d'une classe ne sont accessibles directement que dans les méthodes de la classe. Quand un objet d'une classe est utilisé, il faut accéder aux propriétés de la classe par des accesseurs.
BUT : encapsulation maximum (garantit l'intégrité de l'objet).
II-B-4. Les méthodes conseillées▲
Pour chaque classe, il faudra être capable de :
- instancier par copie (Instancier_copie_<nom de la classe>) ;
- comparer deux instances d'une même classe (Comparer) ;
- comparer une instance avec une valeur autre qu'une instance du même type (Comparer_valeur) ;
- tracer le contenu de l'instance d'une classe : dans le journal interne (Tracer) ou à l'écran (Ecrire) ;
- contrôler la cohérence de l'instance (Est_coherente).
II-B-5. Les méthodes surchargées d'un objet▲
Elles comportent cinq parties :
- déclare et initialise les variables ;
- vérifie la cohérence des données (programmation défensive) ;
- appelle les méthodes de la classe mère surchargée ;
- traite les données ;
- retourne les résultats.
II-B-6. Les méthodes réécrites d'un objet▲
Elles comportent quatre parties (comme une fonction et une procédure) :
- déclare et initialise les variables ;
- vérifie la cohérence des données (programmation défensive) ;
- traite les données ;
- retourne les résultats.
II-B-7. Règle de nommage des méthodes dans le fichier source C▲
Comme il n'est pas possible de décrire les méthodes à l'intérieur d'une classe, il faut définir une fonction ou une procédure par méthode. Le nom de la fonction ou de la procédure est postface par le nom de l'objet.
Exemple : Soit la méthode Liberer d'un stylo, la fonction associée sera Liberer_stylo.
II-B-8. Exemple simple▲
II-B-8-a. Définition du type en C dans le header▲
typedef
enum
{
ROUGE, VERT, BLEU }
e_Couleur ;
/* --- DESCRIPTION DE LA CLASSE STYLO --- */
typedef
struct
t_Stylo
{
/* le destructeur */
void
(*
Liberer)(
struct
t_Stylo **
this) ;
/* les méthodes */
void
(*
Ecrire)(
struct
t_Stylo *
this,char
*
i_Texte);
e_Couleur (*
Lire_encre)(
struct
t_Stylo *
this) ;
/* une propriété */
e_Couleur Couleur;
}
t_Stylo;
extern
t_Stylo *
Instancier_stylo
(
e_Couleur);
II-B-8-b. Définition de la classe en C dans le fichier source C▲
/* --- DESCRIPTION DES FONCTIONS ASSOCIEES AUX METHODES --- */
void
Ecrire_stylo
(
t_Stylo *
this,char
*
i_Texte)
{
Change_couleur
(
this->
Couleur);
printf
(
"
Texte : <%s>
\n
"
,i_Texte);
return
;
}
e_Couleur Lire_encre_stylo
(
t_Stylo *
this)
{
return
(
this->
Couleur);
}
/* le destructeur */
void
Liberer_stylo
(
t_Stylo **
this)
{
AL_FREE
(*
this);
*
this=(
t_Stylo *
)NULL
;
return
;
}
/* le constructeur */
t_Stylo *
Instancier_stylo
(
e_Couleur i_Couleur)
{
/* allocation d'une instance */
t_Stylo *
this=(
t_Stylo *
)AL_MALLOC
(
sizeof
(
t_Stylo)) ;
if
(
this==(
t_Stylo *
)NULL
)
return
((
t_Stylo *
)NULL
) ;
/* affectation des méthodes */
this->
Ecrire=
Ecrire_stylo ;
this->
Lire_encre=
Lire_encre_stylo;
this->
Liberer=
Liberer_stylo;
/* initialisation des propriétés */
this->
Couleur=
i_Couleur;
/* retour de l'objet instancié */
return
(
this);
}
II-B-8-c. Utilisation en C▲
void
ma_fonction
(
)
{
TStylo *
Stylo_Bleu=
Instancier_stylo (
Bleu);
/* ... */
Stylo_Bleu->
Ecrire
(
Stylo,"
Coucou
"
);
/* ... */
Stylo_Bleu->
Liberer
(&
Stylo);
return
;
}
II-C. Les associations▲
II-C-1. Descriptions▲
Les associations sont des liens entre deux (ou plus) d'objets.
II-C-2. Multiplicité d'une association (cardinalité)▲
II-C-2-a. (Zéro, un ou plusieurs) et (Exactement Un)▲
Exemple :
La Classe 1 doit ajouter une propriété qui est une référence (un pointeur) sur une instance de la Classe 2 (ce pointeur ne doit pas être NULL).
La Classe 2 doit avoir une liste de références sur des objets de la Classe 1.
II-C-2-b. (Zéro, un ou plusieurs) et (Zéro, un ou plusieurs)▲
Exemple :
La Classe 1 doit ajouter une liste de références sur des instances de la Classe 2.
La Classe 2 doit ajouter une liste de références sur des instances de la Classe 1.
II-C-2-c. Zéro ou Un▲
Dans ce cas, il faut créer une référence sur une instance associée. Cette référence peut ne pas être renseignée (mise à NULL). (Cf. Exactement Un.)
II-C-2-d. Spécifié numériquement▲
Dans ce cas, comme le nombre d'éléments est connu à l'avance, il faut créer un tableau de références sur les instances associées de la taille spécifiée numériquement.
II-C-3. Ordre d'une association▲
La notion d'ordre est un facteur qui entre en compte à partir du moment où une association peut comprendre une cardinalité n. Cette information constitue une propriété de la liste ou du tableau : Ordre de classement des informations.
La liste ou le tableau peut être classé par ordre croissant, décroissant, pile ou file.
II-C-4. Association n-aire▲
Il n'y aura pas d'associations n-aire. Si une association n-aire se dessine, il faut la décomposer en plusieurs associations binaires.
II-C-5. Attributs d'une association▲
Si une association dispose d'attributs, il faut la transformer en une classe possédant comme propriétés les attributs de l'association et positionner deux associations entre cette nouvelle classe et les deux classes associées. Après quoi, il faut appliquer les règles décrites ci-dessus.
II-C-6. Méthodes obligatoires pour la gestion des associations▲
Il faut définir les méthodes suivantes :
- Ajouter_reference_<nom de la classe associée> : ajoute une référence à une liste ;
- Modifier_reference_<nom de la classe associée> : modifie une référence unique ;
- Supprimer_reference_<nom de la classe associée> :supprime une référence d'une liste.
Exemple :
II-C-7. Exemple d'association▲
II-C-7-a. Description d'une relation père enfant (sans attributs)▲
typedef
struct
t_Individu
{
/* méthodes obligatoires */
void
(*
Liberer)(
struct
t_Individu **
);
int
(*
Comparer) (
struct
t_Individu *
,struct
t_Individu *
);
void
(*
Tracer)(
struct
t_Individu *
,e_Trace,char
*
);
e_Booleen (*
Est_coherente)(
struct
t_Individu *
);
void
(*
Interroger)(
struct
t_Individu *
,t_Requete *
,t_Reponse *
);
/* déclaration des méthodes */
e_Booleen (*
Verifie_numero) (
struct
t_Individu *
);
void
(*
Modifier)(
struct
t_Individu *
,char
*
,char
*
,int
);
int
(*
Lire_age)(
struct
t_Individu *
);
void
(*
Lire_nom_prenom)(
struct
t_Individu *
,int
,char
*
,int
,char
*
);
/* déclaration des propriétés */
char
INSEE[16
];
char
Nom[32
];
char
Prenom[64
];
int
Age;
/* déclaration de l'association */
t_Liste_generique *
Ref_enfant;
struct
t_Individu *
Pére; /* NULL quand pas de père */
}
t_Individu;
extern
t_Individu *
Instancier_individu
(
char
*
,char
*
,char
*
,int
);
extern
t_Individu *
Instancier_copie_individu
(
t_Individu *
);
II-C-7-b. Description d'une relation père enfant (avec attributs)▲
typedef
struct
t_Individu
{
/* méthodes obligatoires */
void
(*
Liberer)(
struct
t_Individu **
);
int
(*
Comparer) (
struct
t_Individu *
,struct
t_Individu *
);
void
(*
Tracer)(
struct
t_Individu *
,e_Trace,char
*
);
e_Booleen (*
Est_coherente)(
struct
t_Individu *
);
void
(*
Interroger)(
struct
t_Individu *
,t_Requete *
,t_Reponse *
);
/* déclaration des méthodes */
e_Booleen (*
Verifie_numero) (
struct
t_Individu *
);
void
(*
Modifier)(
struct
t_Individu *
,char
*
,char
*
,int
);
int
(*
Lire_age)(
struct
t_Individu *
);
void
(*
Lire_nom_prenom)(
struct
t_Individu *
,int
,char
*
,int
,char
*
);
int
(*
Demander_pere)(
struct
t_Individu *
,struct
t_Individu **
);
/* déclaration des propriétés */
char
INSEE[16
];
char
Nom[32
];
char
Prenom[64
];
int
Age;
/* déclaration de la relation */
t_Liste_générique *
Ref_garde;
void
*
Garde_par; /* Pointeur sur une instance de t_Garde */
}
t_Individu;
extern
t_Individu *
Instancier_individu
(
char
*
,char
*
,char
*
,int
);
extern
t_Individu *
Instancier_copie_individu
(
char
*
,char
*
,char
*
,int
);
typedef
struct
t_Garde
{
/* méthodes obligatoires */
void
(*
Liberer)(
struct
t_Garde **
);
/* déclaration des liens de la relation */
t_Individu *
Père;
t_Individu *
Enfant;
/* déclaration des propriétés */
e_Booleen Garde;
}
t_Garde;
/* constructeur */
extern
t_Garde *
Instancier_garde (
t_Individu *
,t_Individu *
,e_Booleen i_Garde);
II-D. L'agrégation▲
L'agrégation sert à définir l'ensemble des composants attachés à une classe.
II-D-1. Exactement Un▲
Exemple :
La Classe 1 doit ajouter une propriété qui est une référence (un pointeur) sur une instance de la Classe 2 (le pointeur ne peut pas être NULL).
II-D-2. Zéro, Un ou plus▲
Exemple :
La Classe 1 doit ajouter une liste de références sur des instances de la Classe 2 (Liste éventuellement vide).
II-D-3. Zéro ou Un▲
Dans ce cas, il faut créer une référence sur une instance agrégée. Cette référence peut ne pas être renseignée (le pointeur peut être éventuellement NULL).
II-D-4. Spécifié numériquement▲
Dans ce cas, comme le nombre d'éléments est connu à l'avance, il faut créer un tableau de références sur les instances agrégées de la taille spécifiée numériquement.
II-D-5. Algorithme de l'instanciation▲
Lors de l'instanciation d'une classe, utilisant des agrégations, il ne faut pas oublier d'initialiser à NULL les composants si cela est nécessaire ou de les instancier.
II-D-6. Exemple▲
typedef
struct
t_Feuille
{
/* ... */
}
t_Feuille;
typedef
struct
t_Branche
{
/* ... */
t_Liste_feuille *
Feuilles; /* ensemble de feuilles situées sur la branche */
/* ... */
}
t_Branche;
typedef
struct
t_Arbre
{
/* ... */
t_Liste_feuille *
Feuilles; /* ensemble des feuilles sur l'arbre */
t_Branche *
Branche; /* la seule branche de l'arbre */
/* ... */
}
t_Arbre;
II-E. L'héritage▲
L'héritage est un mécanisme permettant de mettre en commun un ensemble de propriétés et de méthodes liées à des classes plus spécifiques les unes que les autres (ex. : un fonctionnaire hérite des informations d'un individu, un professeur hérite des informations d'un fonctionnaire, donc d'un individu).
Comme nous considérons que les propriétés d'une classe sont des propriétés privées et les méthodes sont protégées, nous devons accéder aux propriétés de la classe mère avec les accesseurs de la classe mère.
II-E-1. L'héritage simple sans opérations abstraites▲
L'héritage sans opérations abstraites entre la fille et la mère est géré comme une agrégation de cardinalité (1,1). La différence entre une agrégation et un héritage est la nécessité d'instancier la mère en même temps que la fille (c'est-à-dire que lors de l'instanciation d'une fille, il faut instancier obligatoirement une mère : surcharge du constructeur de la mère).
Les méthodes et les propriétés de la classe mère sont considérées privées.
II-E-2. L'héritage multiple▲
L'héritage multiple sans opérations abstraites entre la fille et les mères est géré comme autant d'agrégations de cardinalité (1,1) entre la fille et les mères. Dans ce cas, il ne faut pas oublier d'instancier automatiquement toutes les mères lorsqu'une fille est instanciée.
II-E-3. Les classes abstraites▲
Si une classe manipule des méthodes abstraites (prototype défini au niveau de la mère dont le corps doit être écrit spécifiquement pour les classes filles), c'est une classe abstraite. La classe mère est déclarée comme une classe simple et s'utilise comme un héritage simple.
Si la classe abstraite ne contient que des méthodes abstraites, il n'est pas nécessaire d'appliquer une agrégation. Il faut toujours respecter la règle suivante :
Les méthodes de la classe fille doivent être décrites dans le même ordre que les méthodes de la classe mère en tête de la définition de la structure.
II-E-4. Les classes héritières particulières▲
Il existe des classes héritières qui ne possèdent pas plus de méthodes et d'attributs que la classe mère. Quelques méthodes sont spécifiques, mais dans l'ensemble, l'aspect fonctionnel et technique est le même. Dans ce cas-là, il n'est pas nécessaire de créer les classes filles, il suffit de positionner un indicateur qui spécifie la nature de la classe mère.
Exemple : Soit la classe mère Liste_générique, qui décline en liste générique croissante et décroissante, toutes les fonctions sont quasiment identiques à l'exception des méthodes de rangement. Dans ce cas, il n'est pas nécessaire de décrire les classes liste générique croissante ou décroissante, il suffit de positionner une nature dans la classe mère.
II-E-5. Surcharge des méthodes▲
La méthode surchargée doit faire appel à la méthode de la mère à surcharger avant d'effectuer le traitement propre à la classe fille.
II-E-6. Remarque▲
Pour le codage des héritages en C, il ne faut pas accéder directement aux informations de la mère. Il faut utiliser les accesseurs de la mère (Définition des propriétés privées).
II-E-7. Exemple d'héritage d'une classe simple sans opération abstraite▲
II-E-7-a. Le modèle objet▲
II-E-7-b. Dans le header▲
#ifndef _individu_h
#define _individu_h
typedef
struct
t_Individu
{
void
(*
Liberer)(
struct
t_Individu **
); /* destructeur */
char
*(*
Lire_nom)(
struct
t_Individu *
);
int
(*
Lire_age)(
struct
t_Individu *
);
char
Nom[32
];
char
Prenom[64
];
}
t_Individu;
extern
t_Individu *
Instancier_individu
(
char
*
,int
);
typedef
struct
t_Etudiant /* dérive de t_Individu */
{
void
(*
Liberer)(
struct
t_Etudiant **
); /* destructeur */
char
*(*
Lire_nom)(
struct
t_Etudiant *
); /* méthode surchargée */
int
(*
Lire_age)(
struct
t_Etudiant *
); /* méthode surchargée */
char
*(*
Lire_classe)(
struct
t_Etudiant *
);
t_Individu *
Individu;
char
Classe[256
];
}
t_Etudiant;
extern
t_Etudiant *
Instancier_etudiant
(
char
*
,int
,char
*
);
#endif
II-E-7-c. Dans le fichier source▲
#include "individu.h"
void
Liberer_individu
(
t_Individu **
this)
{
AL_FREE
(*
this);
*
this=(
t_Individu *
)NULL
;
return
;
}
char
*
Lire_nom_individu
(
t_Individu *
this)
{
return
(
this->
Nom);
}
int
Lire_age_individu
(
t_Individu *
this)
{
return
(
this->
Prenom);
}
t_Individu *
Instancier_individu
(
char
*
i_Nom,int
i_Age)
{
t_Individu *
Nouveau=(
t_Individu *
)AL_MALLOC
(
sizeof
(
t_Individu));
/* création d'une nouvelle instance */
if
(
Nouveau==(
t_Individu *
)NULL
)
return
((
t_Individu *
)NULL
);
/* positionnement des méthodes */
Nouveau->
Liberer=
Liberer_individu;
Nouveau->
Lire_nom=
Lire_nom_individu;
Nouveau->
Lire_age=
Lire_age_individu;
/* positionnement des propriétés */
(
void
) strcpy
(
Nouveau->
Nom,i_Nom);
Nouveau->
Age=
i_Age;
return
(
Nouveau);
}
void
Liberer_etudiant
(
t_Etudiant **
this)
{
if
(*
this!=(
t_Etudiant *
)NULL
)
{
/* surcharge du destructeur de la classe mère */
(*
this)->
Individu->
Liberer
(&(*
this)->
Individu);
AL_FREE
(*
this);
}
*
this=(
t_Etudiant *
)NULL
;
return
;
}
char
*
Lire_nom_etudiant
(
t_Etudiant *
this) /* surcharge de Lire_nom_individu */
{
return
(
this->
Individu->
Lire_nom
(
this->
Individu));
}
int
Lire_age_etudiant
(
t_Etudiant *
this) /* surcharge de Lire_age_individu */
{
return
(
this->
Individu->
Lire_age
(
this->
Individu));
}
char
*
Lire_classe_etudiant
(
t_Etudiant *
this)
{
return
(
this->
Classe);
}
t_Etudiant *
Instancier_etudiant
(
char
*
i_Nom,int
i_Age,char
*
i_Classe)
{
t_Etudiant *
Nouveau=(
t_Etudiant *
)AL_MALLOC
(
sizeof
(
t_Etudiant));
if
(
Nouveau==(
t_Etudiant *
)NULL
)
{
return
((
t_Etudiant *
)NULL
);
}
/* instanciation de la classe mère */
Nouveau->
Individu=
Instancier_individu
(
i_Nom,i_Age);
if
(
Nouveau->
Individu==(
t_Individu *
)NULL
)
{
AL_FREE
(
Nouveau);
return
((
t_Etudiant *
)NULL
);
}
/* positionnement des méthodes de la classe fille */
Nouveau->
Lire_nom=
Lire_nom_etudiant;
Nouveau->
Lire_age=
Lire_age_etudiant;
Nouveau->
Lire_classe=
Lire_classe_etudiant;
Nouveau->
Liberer=
Liberer_etudiant;
/* positionnement des propriétés de la classe fille */
(
void
) strcpy
(
Nouveau->
Classe,i_Classe);
return
(
Nouveau);
}
II-E-7-d. Exemple d'utilisation▲
#include "individu.h"
void
main
(
void
)
{
t_Individu *
Eleve=(
t_Individu *
) Instancier_etudiant
(
"
DUPONT Jean
"
,10
,"
DEUG
"
);
if
(
Eleve==(
t_Individu *
)NULL
)
{
printf
(
"
Erreur d'allocation mémoire ...
\n
"
);
return
;
}
printf
(
"
Nom : %s
\n
"
,Eleve->
Lire_nom
(
Eleve));
printf
(
"
Age : %d
\n
"
,Eleve->
Lire_age
(
Eleve));
/* je veux accéder à la méthode Lire_classe de l'étudiant */
/* ATTENTION : ceci peut être réalisé, car je sais que c'est un étudiant */
printf
(
"
Classe : %s
\n
"
,((
t_Etudiant *
)Eleve)->
Lire_classe
(
Eleve));
Eleve->
Liberer
(&
Eleve);
return
;
}
II-E-8. Exemple d'une expression booléenne▲
II-E-8-a. Modèle objet▲
II-E-8-b. Description du header▲
#ifndef _expression_h
#define _expression_h
typedef
enum
{
OUI=
1
, NON=
0
, VRAI=
1
, FAUX=
0
, TRUE=
1
, FALSE=
0
}
e_Booleen;
typedef
enum
{
ET, OU }
e_Operateur_binaire;
typedef
enum
{
NON }
e_Operateur_unaire;
typedef
struct
t_Expression_booleenne
{
/* méthode abstraite */
/* ----------------- */
void
(*
Liberer)(
struct
t_Expression_booleenne **
);
e_Booleen (*
Evaluer)(
struct
t_Expression_booleenne *
);
}
t_Expression_booleenne;
typedef
struct
t_Constante
{
/* méthode abstraite */
/* ----------------- */
void
(*
Liberer)(
struct
t_Constante **
);
e_Booleen (*
Evaluer)(
struct
t_Constante *
);
/* propriété de la classe */
/* ---------------------- */
int
Valeur;
}
t_Constante;
extern
t_Constante *
Instancier_constante
(
int
Valeur);
typedef
struct
t_Unaire
{
/* méthode abstraite */
/* ----------------- */
void
(*
Liberer)(
struct
t_Unaire **
);
e_Booleen (*
Evaluer)(
struct
t_Unaire *
);
/* propriété */
/* --------- */
e_Operateur_unaire Operateur;
t_Expression_booleenne *
Membre;
}
t_Unaire;
extern
t_Unaire *
Instancier_unaire
(
char
Operateur,t_Expression_booleenne *
Membre);
typedef
struct
t_Binaire
{
/* méthode abstraite */
/* ----------------- */
void
(*
Liberer)(
struct
t_Binaire **
);
e_Booleen (*
Evaluer)(
struct
t_Binaire *
);
/* propriété */
/* --------- */
e_Operateur_binaire Operateur;
t_Expression_booleenne *
Gauche;
t_Expression_booleenne *
Droite;
}
t_Binaire;
extern
t_Binaire *
Instancier_binaire
(
char
Operateur, t_Expression_booleenne *
Gauche, t_Expression_booleenne *
Droite);
#endif
II-E-8-c. La description du fichier source C▲
#include <stdio.h>
#include "expression.h"
e_Booleen Evaluer_constante
(
t_Constante *
this)
{
return
(
this->
Valeur);
}
void
Liberer_constante
(
t_Constante **
this)
{
/* libération de l'instance */
AL_FREE
(*
this);
/* positionnement de l'instance a NULL */
*
this=(
t_Constante *
)NULL
;
return
;
}
t_Constante *
Instancier_constante
(
int
Valeur)
{
/* allocation */
t_Constante *
Nouveau=(
t_Constante *
)AL_MALLOC
(
sizeof
(
t_Constante));
if
(
Nouveau==(
t_Constante *
)NULL
)
return
((
t_Constante *
)NULL
);
/* positionnement de méthodes */
Nouveau->
Evaluer=
Evaluer_Constante;
Nouveau->
Liberer=
Liberer_Constante;
/* positionnement de la valeur */
Nouveau->
Valeur=
Valeur;
return
(
Nouveau);
}
e_Booleen Evaluer_unaire
(
t_Unaire *
this)
{
if
(
this->
Operateur==
NON)
{
return
(!
this->
Membre->
Evaluer
(
this->
Membre));
}
return
(
0
);
}
void
Liberer_unaire
(
t_Unaire **
this)
{
(*
this)->
Membre->
Liberer
(&(*
this)->
Membre);
AL_FREE
(*
this);
*
this =
(
t_Unaire *
)NULL
;
return
;
}
t_Unaire *
Instancier_unaire
(
e_Operateur_unaire Operateur,
t_Expression_booleenne *
Membre)
{
/* allocation */
t_Unaire *
Nouveau=(
t_Unaire *
)AL_MALLOC
(
sizeof
(
t_Unaire));
if
(
Nouveau==(
t_Unaire *
)NULL
)
return
((
t_Unaire *
)NULL
);
/* positionnement de méthodes */
Nouveau->
Evaluer=
Evaluer_Unaire;
Nouveau->
Liberer=
Liberer_Unaire;
/* positionnement de la valeur */
Nouveau->
Operateur=
Operateur;
Nouveau->
Membre=
Membre;
return
(
Nouveau);
}
e_Booleen Evaluer_binaire
(
t_Binaire *
this)
{
e_Booleen Valeur=
FAUX;
switch
(
this->
Operateur)
{
case
ET : Valeur=
this->
Gauche->
Evaluer
(
this->
Gauche)&&
this->
Droite->
Evaluer
(
this->
Droite);
break
;
case
OU : Valeur=
this->
Gauche->
Evaluer
(
this->
Gauche)||
this->
Droite->
Evaluer
(
this->
Droite);
break
;
}
return
(
Valeur);
}
void
Liberer_binaire
(
t_Binaire **
this)
{
(*
this)->
Gauche->
Liberer
(&(*
this)->
Gauche);
(*
this)->
Droite->
Liberer
(&(*
this)->
Droite);
AL_FREE
(*
this);
*
this=(
t_Binaire *
)NULL
;
return
;
}
t_Binaire *
Instancier_binaire
(
e_Operateur_binaire Operateur, t_Expression_booleenne *
Gauche, t_Expression_booleenne *
Droite)
{
/* allocation */
t_Binaire *
Nouveau=(
t_Binaire *
)AL_MALLOC
(
sizeof
(
t_Binaire));
if
(
Nouveau==(
t_Binaire *
)NULL
)
return
((
t_Binaire *
)NULL
);
/* positionnement de méthodes */
Nouveau->
Evaluer=
Evaluer_binaire;
Nouveau->
Liberer=
Liberer_binaire;
/* positionnement de la valeur */
Nouveau->
Operateur=
Operateur;
Nouveau->
Gauche=
Gauche;
Nouveau->
Droite=
Droite;
return
(
Nouveau);
}
II-E-8-d. Un programme d'exemple▲
#include <stdio.h>
#include "expression.h"
void
main
(
)
{
e_Booleen Valeur;
t_Expression_booleenne *
Expr=(
t_Expression_booleenne *
)Instancier_binaire
(
ET,
(
t_Expression_booleenne *
)Instancier_unaire
(
NON,
(
t_Expression_booleenne *
)Instancier_constante
(
1
)),
(
t_Expression_booleenne *
)Instancier_constante
(
0
));
Valeur =
Expr->
Evaluer
(
Expr);
printf
(
"
Expr = %s
\n
"
,Valeur?"
VRAI
"
:"
FAUX
"
);
Expr->
Liberer
(&
Expr);
return
;
}
II-F. Les patrons ou les modèles▲
II-F-1. Description d'un patron▲
Un patron est une classe générique offrant un ensemble de méthodes communes, quel que soit le type de l'objet manipulé.
Exemple : une liste générique.
Les fonctionnalités (de la gestion d'une liste de produits ou d'individus seront les mêmes (Ajout, Suppression, Parcours…).
Un patron gérera un objet comme étant une référence sur un objet quelconque (void *). Pour manipuler cet objet, il faudra définir un certain nombre de méthodes propres à l'objet défini lors de l'instanciation d'un patron. Le patron n'est donc pas responsable de l'implémentation de ces méthodes, seule la classe de l'objet doit définir ces méthodes. En d'autres termes, ce sont un ensemble de classes partageant un même ensemble de méthodes.
Exemple : Une liste croissante générique.
Cet exemple manipule n'importe quel objet, mais le patron doit savoir comment sont classés les objets. Il a donc besoin d'une fonction de comparaison entre deux objets de liste la pour les classer.
II-F-2. Exemple d'un patron : Liste_Générique_Croissante▲
typedef
struct
t_Liste_generique_croissante
{
/* destructeur */
void
(*
Liberer)(
struct
t_Liste_generique_croissante **
);
/* méthodes du patrons */
void
(*
Ajouter)(
struct
t_Liste_generique_croissante *
,void
*
);
void
(*
Supprimer)(
struct
t_Liste_generique_croissante *
,void
*
);
void
*(*
Rechercher)(
struct
t_Liste_generique_croissante *
,void
*
);
/* méthode propre à l'objet à généraliser */
int
(*
Comparer)(
void
*
,void
*
);
/* propriétés du patron */
int
Nb_Elements;
void
**
Liste_element;
}
t_Liste_générique_croissante;
extern
t_Liste_generique_croissante *
Instancier_Liste_generique_croissante
(
int
(*
Comparer)(
void
*
,void
*
));
II-G. Les classes interfaces▲
II-G-1. Définition▲
Une classe interface est une classe dont seules les méthodes sont visibles de l'extérieure par les classes qui les utilisent.
II-G-2. Codage d'une classe interface▲
II-G-2-a. Dans le header▲
Dans le header de la classe interface, doit apparaître uniquement la description de cette dernière avec les méthodes. Les différentes instanciations possibles doivent y figurer aussi.
II-G-2-b. Dans le fichier source▲
Dans le fichier source de la classe interface, doit figurer la description de toutes les classes implémentant la classe interface. Ces nouvelles classes peuvent disposer de méthodes supplémentaires et/ou de propriétés.
II-G-3. Exemple d'une classe interface▲
II-G-3-a. Description▲
II-G-3-b. Dans le header▲
#ifndef _compteur_h
#define _compteur_h
typedef
struct
t_Compteur
{
void
(*
Liberer)(
struct
t_Compteur **
);
void
(*
Initialiser)(
struct
t_Compteur *
);
void
(*
Incrementer)(
struct
t_Compteur *
);
void
(*
Decrementer)(
struct
t_Compteur *
);
int
(*
Lire_valeur)(
struct
t_Compteur *
);
}
t_Compteur;
extern
t_Compteur *
Instancier_compteur_sequentiel
(
int
Mini, int
Maxi);
extern
t_Compteur *
Instancier_compteur_circulaire
(
int
Mini,int
Maxi);
#endif
II-G-3-c. Dans le fichier source▲
#include <stdio.h>
#include "compteur.h"
typedef
struct
t_Compteur_sequentiel
{
void
(*
Liberer)(
struct
t_Compteur_sequentiel **
);
void
(*
Initialiser)(
struct
t_Compteur_sequentiel *
);
void
(*
Incrementer)(
struct
t_Compteur_sequentiel *
);
void
(*
Decrementer)(
struct
t_Compteur_sequentiel *
);
int
(*
Lire_valeur)(
struct
t_Compteur_sequentiel *
);
int
Compteur_courant;
int
Mini,Maxi;
}
t_Compteur_sequentiel;
void
Initialiser_sequentiel
(
t_Compteur_sequentiel *
this)
{
this->
Compteur_courant =
this->
Mini;
return
;
}
void
Incrementer_sequentiel
(
t_Compteur_sequentiel *
this)
{
if
(
this->
Compteur_courant !=
this->
Maxi)
this->
Compteur_courant ++
;
return
;
}
void
Decrementer_sequentiel
(
t_Compteur_sequentiel *
this)
{
if
(
this->
Compteur_courant !=
this->
Mini)
this->
Compteur_courant --
;
return
;
}
int
Lire_valeur_compteur_sequentiel
(
t_Compteur_sequentiel *
this)
{
return
(
this->
Compteur_courant);
}
void
Liberer_compteur_sequentiel
(
t_Compteur_sequentiel **
this)
{
AL_FREE
(*
this);
*
this =
(
t_Compteur_sequentiel *
)NULL
;
return
;
}
t_Compteur *
Instancier_compteur_sequentiel
(
int
Mini,int
Maxi)
{
t_Compteur_sequentiel *
Nouveau=(
t_Compteur_sequentiel *
) AL_MALLOC
(
sizeof
(
t_Compteur_sequentiel));
if
(
Nouveau ==
(
t_Compteur_sequentiel *
)NULL
)
return
((
t_Compteur *
)NULL
);
Nouveau->
Initialiser=
Initialiser_compteur_sequentiel;
Nouveau->
Incrementer=
Incrementer_compteur_sequentiel;
Nouveau->
Decrementer=
Decrementer_compteur_sequentiel;
Nouveau->
Lire_valeur=
Lire_valeur_compteur_sequentiel;
Nouveau->
Liberer=
Liberer_compteur_sequentiel;
if
(
Mini <
Maxi)
{
Nouveau->
Mini =
Mini;
Nouveau->
Maxi =
Maxi;
}
else
{
Nouveau->
Mini =
Maxi;
Nouveau->
Maxi =
Mini;
}
return
((
t_Compteur *
)Nouveau);
}
typedef
struct
t_Compteur_circulaire
{
void
(*
Liberer)(
struct
t_Compteur_circulaire **
);
void
(*
Initialiser)(
struct
t_Compteur_circulaire *
);
void
(*
Incrementer)(
struct
t_Compteur_circulaire *
);
void
(*
Decrementer)(
struct
t_Compteur_circulaire *
);
int
(*
Lire_valeur)(
struct
t_Compteur_circulaire *
);
int
Compteur_courant;
int
Mini,Maxi;
}
t_Compteur_circulaire;
void
Initialiser_circulaire
(
t_Compteur_circulaire *
this)
{
this->
Compteur_courant =
this->
Mini;
return
;
}
void
Incrementer_circulaire
(
t_Compteur_circulaire *
this)
{
if
(
this->
Compteur_courant !=
this->
Maxi)
this->
Compteur_courant ++
;
else
this->
Compteur_courant =
this->
Mini;
return
;
}
void
Decrementer_circulaire
(
t_Compteur_circulaire *
this)
{
if
(
this->
Compteur_courant !=
this->
Mini)
this->
Compteur_courant --
;
else
this->
Compteur_courant =
this->
Maxi;
return
;
}
int
Lire_valeur_compteur_circulaire
(
t_Compteur_circulaire *
this)
{
return
(
this->
Compteur_courant);
}
void
Liberer_compteur_circulaire
(
t_Compteur_circulaire **
this)
{
AL_FREE
(*
this);
*
this =
(
t_Compteur_circulaire *
)NULL
;
return
;
}
t_Compteur *
Instancier_compteur_circulaire
(
int
Mini,int
Maxi)
{
t_Compteur_circulaire *
Nouveau=(
t_Compteur_circulaire *
) AL_MALLOC
(
sizeof
(
t_Compteur_circulaire));
if
(
Nouveau ==
(
t_Compteur_circulaire *
)NULL
)
return
((
t_Compteur *
)NULL
);
Nouveau->
Initialiser=
Initialiser_compteur_circulaire;
Nouveau->
Incrementer=
Incrementer_compteur_circulaire;
Nouveau->
Decrementer=
Decrementer_compteur_circulaire;
Nouveau->
Lire_valeur=
Lire_valeur_compteur_circulaire;
Nouveau->
Liberer=
Liberer_compteur_circulaire;
if
(
Mini <
Maxi)
{
Nouveau->
Mini =
Mini;
Nouveau->
Maxi =
Maxi;
}
else
{
Nouveau->
Mini =
Maxi;
Nouveau->
Maxi =
Mini;
}
return
((
t_Compteur *
)Nouveau);
}
II-H. Ordre de déclaration des éléments d'un type▲
Dans la structure permettant de modéliser une classe, il faut définir :
- Le destructeur ;
- Les méthodes abstraites (publiques) ;
- Les méthodes surchargées (protégées) ;
- Les méthodes propres à la classe (privées) ;
- Les propriétés ;
- Les agrégations ;
- Les associations.
II-I. La gestion des erreurs (mécanisme d'exceptions)▲
Pour gérer les erreurs survenant dans une classe, un peu comme des exceptions, il faut ajouter une propriété : Erreur de type e_Erreur. Ce type énuméré recense toutes les exceptions utilisées dans cette bibliothèque. Cette propriété doit alors être mise à jour dans toutes les méthodes ou accesseurs (sauf Lire_erreur) pour savoir si le dernier appel est responsable d'une anomalie ou pas.
Pour accéder à cette propriété, il faut alors ajouter un accesseur : Lire_erreur.