Licence CC BY-NC-SA

Créer vos propres types

Nous avons déjà abordé de nombreux types (Integer, Float, Character, String, tableaux et j'en passe) mais peu d'entre eux suffiraient à modéliser la plupart des objets de la vie courante. Imaginez que vous vouliez modéliser l'identité des personnages de votre futur jeu vidéo révolutionnaire : des String modèliseraient les nom et prénom, un Integer peut modéliser l'âge, des Float pourraient modéliser la taille et le poids… mais aucun type ne peut à lui seul modéliser l'ensemble de ces données, même pas les tableaux. De même, comment modéliser une pendule ? Pour les Integer, 23 + 2 = 25, alors que pour une pendule 23H + 2H donne 1H du matin !

Nous allons donc apprendre à créer nos propres types composites, à les modifier, à les faire évoluer et même à créer des «sous-types» !

Créer à partir de types prédéfinis

Cette partie n'est pas la plus folichonne : nous allons réutiliser des types préexistants pour en créer des nouveaux. Malgré tout, elle vous sera souvent très utile et elle n'est pas très compliquée à comprendre.

Sous-type comme intervalle

Schématiquement, Integer couvre les nombres entiers (0, 1, 2, 3… -1, -2, -3…) et natural les nombres entiers naturels (0, 1, 2, 3…). Nous souhaiterions créer un type T_positif et un type T_negatif qui ne comportent pas le nombre 0. Pour cela, nous allons créer un sous-type (SUBTYPE) de natural ou de integer qui couvrira l'intervalle (RANGE) allant de 1 à l'infini (symbole : $+\infty$) pour les positifs et de «l'infini négatif» (symbole : $-\infty$) à -1 pour les négatifs.

L'infini n'existe pas en informatique. Les nombres générés sont nécessairement limités, quand bien même cette limite est très élevée, il ne s'agit pas de l'infini au sens mathématique.

Nous allons donc, dans la partie déclarative, écrire soit :

1
2
subtype T_positif is integer range 1..integer'last ; 
subtype T_negatif is integer range integer'first..-1 ;

Soit :

1
subtype T_positif is natural range 1..natural'last ; 

Il sera alors impossible d'écrire ceci :

1
2
n : T_positif := 0 ; 
m : T_negatif := 1 ;

Comme T_positif et T_negatif dérivent des types integer ou natural (lui-même dérivant du type integer), toutes les opérations valables avec les integer/natural, comme l'addition ou la multiplication, sont bien sûr valables avec les types T_positif et T_negatif, tant que vous ne sortez pas des intervalles définis.

Types modulaires

Deuxième exemple, nous souhaiterions créer un type T_chiffre allant de 0 à 9. Nous écrirons alors :

1
subtype T_chiffre is integer range 0..9 ;

Seulement, les opérations suivantes engendreront une erreur :

1
2
3
4
5
6
...
   c : T_chiffre ;
BEGIN
   c:= 9 ;      --Jusque là, tout va bien
   c:= c+2 ;    --c devrait valoir 11. Aïe ! Pas possible !
...

Il serait donc plus pratique si après 9, les comptes recommençaient à 0 ! Dans ce cas 9+1 donnerait 0, 9+2 donnerait 1, 9+3 donnerait 2… C'est à cela que servent les sous-types modulaires que l'on définira ainsi :

1
type T_chiffre is mod 10;

Le type T_chiffre comportera 10 nombres : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ! Pour ceux qui ont de bonnes connaissances en mathématiques, X = 9 équivaudra à écrire $X \equiv 9 (10)$, c'est-à-dire que « $X$ est congru à 9 modulo 10 ».

Autre exemple, nous allons créer des types T_Heure, T_Minute, T_Seconde, T_Milliseconde pour indiquer le temps. Remarquez que depuis le début, j'introduis mes types par la lettre T, ce qui permet de distinguer rapidement si un mot correspond à un type ou à une variable.

1
2
3
4
5
type T_Heure is mod 24 ;
type T_Minute is mod 60 ; 
type T_Seconde is mod 60 ; 
type T_Milliseconde is mod 1_000         -- on peut aussi écrire 1000, mais le symbole _ (touche 8) 
                                         -- permet de séparer les chiffres pour y voir plus clair

Le nombre écrit après MOD n'est pas le nombre maximal du sous-type mais le nombre d'éléments contenus dans ce sous-types, le premier étant 0. Par exemple, ces déclarations interdisent donc à une variable H de type T_Heure de valoir 24 ! Si elle vaut 23 et qu'on l'incrémente (+1) alors elle retournera à 0. De même une variable mil de type T_Milliseconde peut prendre une valeur entre 0 et 999 mais pas 1000 !

Une variable M de type T_Minute n'est plus un integer ou un natural ! C'est un type modulaire au fonctionnement un peu différent. Pour l'afficher, par exemple, il faudra écrire « Put(integer(M)) ».

Énumérer les valeurs d'un type

Continuons avec notre exemple du temps. Les types T_Heure, T_Minute, T_Seconde et T_Milliseconde ont été créés comme des types modulaires. Créer un type T_Annee ne devrait pas vous poser de soucis. Très bien. Mais pour les jours ou les mois, il serait bon de distinguer trois types distincts :

  • T_Numero_Jour : type allant de 1 à 31 (non modulaire car commençant à 1 et non 0)
  • T_Nom_Jour : type égal à LUNDI, MARDI, MERCREDI…
  • T_Mois : type égal à JANVIER, FÉVRIER, MARS…

Nous savons déclarer le premier type :

1
subtype T_Numero_Jour is integer range 1..31 ;

Pour les suivants, il faut créer ce que l'on appelle un type énuméré (un type où il faut énumérer toutes les valeurs disponibles), de la manière suivante :

1
2
3
type T_Mois is (JANVIER,FEVRIER,MARS,AVRIL,MAI,JUIN,
                    JUILLET,AOUT,SEPTEMBRE,OCTOBRE,NOVEMBRE,DECEMBRE) ; 
type T_Nom_Jour is (LUNDI,MARDI,MERCREDI,JEUDI,VENDREDI,SAMEDI,DIMANCHE) ;

Pourquoi ne pas se contenter d'un nombre pour représenter le jour ou le mois ?

Tout simplement par souci de clarté et de mémorisation. Est-il plus simple d'écrire : «jour = 4» ou «jour = JEUDI» ? «mois := 5» ou «mois := MAI» ? La deuxième façon est bien plus lisible. Souvenez-vous des fichiers et de leur mode d'ouverture : ne vaut-il pas mieux écrire «In_File», «Out_File» ou «Append_File» que 0, 1 ou 2 ? Ne vaut-il mieux pas écrire TRUE ou FALSE que 1 ou 0 ? De plus, il est possible d'utiliser les attributs avec les types énumérés :

  • T_Mois'first renverra JANVIER (le premier mois)
  • T_Nom_Jour'last renverra DIMANCHE (le dernier jour)
  • T_Mois'succ(JUIN) renverra JUILLET (le mois suivant JUIN)
  • T_Mois'pred(JUIN) renverra MAI (le mois précédant JUIN)
  • T_Mois'pos(JUIN) renverra 5 (le numéro du mois de JUIN dans la liste)
  • T_Mois'val(1) renverra FÉVRIER (le mois n°1 dans la liste, voir après)
  • put(T_Nom_Jour'image(MARDI)) permettra d'écire MARDI à l'écran

Les types énumérés sont rangés à partir de 0, donc JANVIER est le mois n°0 et FÉVRIER le mois n°1 !

Par conséquent, il est même possible de comparer des variables de type énuméré : MARDI>JEUDI renverra false ! Enfin, les affectations se font de la manière la plus simple qu'il soit :

1
2
3
4
...
   mois : T_Mois ;
BEGIN
   mois := AOUT ;

Les types structurés

Déclarer un type «construit»

La théorie

Nous disposons maintenant des types T_Heure, T_Minute, T_Seconde, T_Milliseconde, T_Annee (je vous ai laissé libres de le faire), T_Mois, T_Nom_Jour et T_Numero_Jour, ouf ! C'est un peu long tout ça, non ? :waw: Il serait plus pratique de tout mettre dans un seul type de données (T_Temps ou T_Date par exemple) de manière à éviter de déclarer tout un escadron de variables pour enregistrer une simple date.

Un type T_Temps permettrait de rassembler toutes les informations

C'est ce que nous permettent les types articles. Si le terme employé par la terminologie Ada est « Type article », bon nombre de langages parlent plutôt de types structurés ou (d'ailleurs en C, le mot clé est struct). Pour une fois les termes du C me semblent plus clairs et je préférerai donc généralement cette terminologie. :D Nous allons donc créer notre type structuré en écrivant « TYPE T_Temps IS » comme d'habitude puis nous allons ouvrir un bloc RECORD. L'exemple par l'image :

1
2
3
4
type T_Temps is
record
   ...
end record ;

Rappelons que «to record» signifie en anglais «Enregistrer». C'est donc entre les instructions RECORD et END RECORD que nous allons enregistrer le contenu du type T_Temps, c'est-à-dire l'heure, les minutes, les secondes et les millisecondes :

1
2
3
4
5
6
7
type T_Temps is
record
   H : T_Heure ; 
   Min : T_Minute ; 
   S : T_Seconde ; 
   Milli : T_Milliseconde ; 
end record ;

Ainsi, c'est comme si l'on déclarait quatre variables de types différents sauf que ce que l'on déclare, ce sont les quatre composantes de notre type. Par la suite, nous n'aurons plus qu'une seule variable de type T_Temps à déclarer et à modifier. Il est même possible d'attribuer des valeurs par défaut à nos différentes composantes. Ainsi, contrairement aux autres types de variables ou d'objets prédéfinis, il sera possible d'initialiser un objet de type T_Temps simplement en le déclarant.

1
2
3
4
5
6
7
8
9
type T_Temps is
record
   H : T_Heure := 0 ; 
   Min : T_Minute := 0 ; 
   S : T_Seconde := 0 ; 
   Milli : T_Milliseconde := 0 ; 
end record ; 

t : T_Temps ;                    --Sans rien faire, t vaut automatiquement 0 H 0 Min 0 S 0 mS !

Mise en pratique

Voici quelques petits exercices d'application bêtes et méchant.

  • Créer un type T_Jour contenant le nom et le numéro du jour.
  • Créer le type T_Date contenant le jour, le mois et l'année.

Solution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
type T_Jour is
record
   nom : T_Nom_Jour := LUNDI; 
   num : T_Numero_Jour := 1 ; 
end record ; 

type T_Date is
record
   jour : T_Jour ; 
   mois : T_Mois ; 
   annee : T_Annee ; 
end record ;

Ordre des déclarations

Il est très important de déclarer vos types dans l'ordre suivant :

  1. les types T_Annee,T_Mois,T_Nom_Jour,T_Numero_Jour dans n'importe quel ordre ;
  2. le type T_Jour ;
  3. le type T_Date !

En effet, le type T_Jour a besoin pour être déclaré que les types T_Numero_Jour et T_Nom_Jour soient déjà déclarés, sinon, lors de la compilation, le compilateur va tenter de construire notre type T_Jour et découvrir que pour cela il a besoin de deux types inconnus ! Car le compilateur va lire votre code du haut vers le bas et il lève les erreurs au fur et à mesure qu'elles apparaissent.

Pour pallier à ce problème, soit vous respectez l'ordre logique des déclarations, soit vous ne voulez pas (c'est bête) ou vous ne pouvez pas (ça arrive) respecter cet ordre et auquel cas, je vous conseille d'écrire les spécifications des types nécessaires auparavant :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type T_Nom_Jour ;            --Liste des spécifications évitant les soucis à la compilation
type T_Numero_Jour ; 
type T_Jour ; 


type T_Date is               --Définition des types (le tout en vrac)
record
   ...
end record ;

type T_Jour is
record
   ...
end record ; 

type T_Nom_Jour is
record
   ...
end record ; 

type T_Numero_Jour is
record
   ...
end record ;

Déclarer et modifier un objet de type structuré

Déclaration d'un objet de type structuré

Le langage Ada considère automatiquement les types que vous construisez comme les siens. Donc la déclaration se fait le plus simplement du monde, comme pour n'importe quelle variable :

1
2
t : T_Temps ; 
prise_bastille : T_Date ;

Affectation d'un objet de type structuré

Comment attribuer une valeur à notre objet t de type T_Temps ? Il y a plusieurs façons. Tout d'abord avec les agrégats, comme pour les tableaux (à la différence que les valeurs ne sont pas toutes du même type) :

1
2
3
4
t := (11,55,48,763) ; --pensez à respecter l'ordre dans lequel les composantes 
                      --ont été déclarées lors du record : H,Min,S,Milli
                      -- OU BIEN
t := (H => 7, Min => 56, Milli => 604 , S => 37) ; --Plus de soucis d'orde

Cette méthode est rapide, efficace mais pas très lisible. Si on ne connaît pas bien le type T_Temps, on risque de se tromper dans l'ordre des composantes. Et puis, si on veut seulement faire une affectation sur les minutes ? Voici donc une seconde façon, sans agrégats :

1
2
3
4
5
6
7
t.H := 11 ; 
t.S := 48 ;
t.Min := 55 ; 
t.Milli := 763 ; 
      ...
t.Min := t.Min + 15 ;    --Possible car t.Min est la composante de type T_Minute qui est un type modulaire
                         --En revanche, les t.H n'est pas incrémenté pour autant !

Un nouvel exercice et sa correction

À vous d'affecter une valeur à notre objet prise_bastille ! Pour rappel, la Bastille est tombée le 14 juillet 1789 (et c'était un mardi).

1
2
3
4
5
6
prise_bastille := ((MARDI,14),JUILLET,1789) ; 
      --OU BIEN
prise_bastille.jour.nom := MARDI ; 
prise_bastille.jour.num := 14 ; 
prise_bastille.mois := JUILLET ; 
prise_bastille.annee := 1789 ;

Il faudra, pour la deuxième méthode, utiliser deux fois les points pour accéder à la composante de composante.

22 ! Rev'là les tableaux !

Compliquons notre tâche en mélangeant tableaux et dates !

Tableau dans un type

Tout d'abord, il pourrait être utile que certaines dates aient un nom ("Marignan", "La Bastille" , "Gergovie"…) enregistré dans un string de 15 cases (par exemple). Reprenons notre type T_Date :

1
2
3
4
5
6
7
type T_Date is
record
   nom : string(1..15) := "Pas d'An 0 !!! "; 
   jour : T_Jour := (LUNDI,1) ; 
   mois : T_Mois := JANVIER; 
   annee : T_Annee := 0 ; 
end record ;

Il contient désormais un tableau (le string). Les opérations suivantes sont alors possibles :

1
2
3
4
5
6
...
   prise_bastille : T_Date ; 
BEGIN
   prise_bastille.nom := "La Mastique    " ;    --Erreur volontaire, pas d'inquiétude ^^
   prise_bastille.nom(4) := 'B' ;               --Rectifications 
   prise_bastille.nom(9..10) := "ll" ;

Attention, prise_bastille n'est pas un tableau, c'est prise_bastille.nom qui en est un !

Type dans un tableau (Euh… Van Gogh ? :-° )

Plus compliqué, on veut créer un tableau avec des dates à retenir (10 dates par exemple). Une fois notre type T_Date déclaré, déclarons notre type T_Chronologie et une variable T :

1
2
type T_Chronologie is array(1..10) of T_Date ; 
T : T_Chronologie ;

Cette fois c'est T qui est notre tableau et T(1), T(2)… sont des dates. Allons-y pour quelques affectations :

1
2
3
4
5
6
7
8
9
T(1) := ("La Bastille    ",(MARDI,14),JUILLET,1789) ; 

T(2).Nom := "Marignan       " ; 
T(2).Annee := 1515 ; 
T(2).Jour.Num := 14 ; 

T(3).Nom := "Jour G         " ; 
T(3).Nom(6) := 'J' ; 
T(3).Nom(1..12) := "Debarquement" ;

Eh oui, les dernières sont bizarres, non ? Petite explication :

  • T est un tableau ;
  • T(3) est la 3ème case du tableau qui contient une date ;
  • T(3).Nom est un String, soit un tableau contenant des caractères et contenu dans la troisième case du tableau T ;
  • T(3).Nom(6) est la 6ème case du tableau T(3).Nom et c'est le caractère 'G' que l'on modifie ensuite en 'J'.

Les types structurés : polymorphes et mutants !

Les types structurés polymorphes

Prérequis

La notion de type polymorphe était déjà présente dans la norme Ada83, la norme Ada95 a intégré la Programmation Orientée Objet pour laquelle le polymorphisme a un autre sens : le polymorphisme recouvre donc deux aspects en Ada. Nous parlerons pour l'instant de polymorphisme de type, par distinction avec le polymorphisme de classe cher à la POO.

Le polymorphisme est la propriété d'avoir plusieurs formes, plusieurs apparences. En informatique, c'est l'idée de permettre à un type donné, d'avoir plusieurs apparences distinctes. Nos types T_Date et T_Temps ne conviennent plus à cette partie, prenons un nouvel exemple : un type T_Bulletin qui contient les moyennes d'un élève dans différentes matières. Selon que notre élève est en primaire, au collège ou au lycée, il n'aura pas les mêmes matières (nous n'entrerons pas dans le détail des filières et des options par classe, l'exemple doit rester compréhensible, quitte à être caricatural). Considérons les données suivantes :

  • Un élève de primaire a du Français, des Maths et du Sport.
  • Un élève de collège a du Français, des Maths, du Sport, une langue étrangère (LV1) et de la Biologie.
  • Un élève de lycée a du Français, des Maths, du Sport, une Langue Étrangère (LV1), de la Biologie, de la Chimie et une deuxième Langue Étrangère (LV2).

Oui, je sais il manque beaucoup de choses et c'est très loin de la réalité, mais je n'ai pas envie d'écrire des dizaines de composantes ! :-°

Notre type T_Bulletin n'a pas besoin d'avoir une composante Chimie si notre élève est en primaire ; il doit donc s'adapter à l'élève ou plutôt à sa classe ! Nous allons donc définir deux autres types : un type énumératif T_Classe et un type structuré T_Eleve.

1
2
3
4
5
6
7
8
type T_Classe is (PRIMAIRE, COLLEGE, LYCEE) ; 

type T_Eleve is
record
   nom : string(1..20) ;         --20 caractères devraient suffire
   prenom : string(1..20) ;      --on complètera au besoin par des espaces
   classe : T_Classe ;           --Tout l'intérêt d'écrire un T devant le nom de notre type ! ! ! 
end record ;

Créer un type polymorphe T_Bulletin

Maintenant, nous allons pouvoir créer notre type T_Bulletin, mais comme nous l'avons dit, sa structure dépend de l'élève et surtout de sa classe. Il faut donc paramétré notre type T_Classe !

Paramétrer un type ? C'est possible ?

Bien sûr, souvenez vous des tableaux ! Le type ARRAY est générique, il peut marcher avec des integer, des float… Il n'y a donc pas de raison que les types articles n'aient pas la même capacité. Pour notre type T_Bulletin, cela devrait donner ceci :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type T_Bulletin(classe : T_Classe) is
record
   --PARTIE FIXE

   francais, math, sport : float ;    --ou float range 0.0..20.0 ; 

   --PARTIE VARIABLE : COLLEGE ET LYCEE

   case classe is
      when PRIMAIRE =>
         null ; 
      when COLLEGE | LYCEE => 
         lv1, bio : float ; 

   --PARTIE SPECIFIQUE AU LYCEE

         case classe is
            when LYCEE => 
               chimie, lv2 : float ; 
            when others =>
               null ; 
         end case ; 
   end case ; 
end record ;

Ce paramétrage nous permet ainsi d'éviter d'avoir à définir plusieurs types T_Bulletin_Primaire, T_Bulletin_College, T_Bulletin_Lycee. Il suffit de fournir en paramètre la classe en question. Attention toutefois ! Le paramètre fournit doit être discret.

Parce qu'un paramètre peut-être bruyant ? o_O

Non, discret n'est pas ici le contraire de bruyant ! C'est une notion mathématique qui signifie que les valeurs sont toutes «isolées les unes des autres» ou, d'une autre façon, si je prends deux valeurs au hasard, il n'y a qu'un nombre fini de valeurs comprises entre les deux que j'ai prises. Par exemple : les integer sont de type discret (entre 2 et 5, il n'y a que 3 et 4, soit 2 nombres) ; les nombres réels (et le type float) ne sont pas discrets (entre 2.0 et 5.0, il y a une infinité de nombres comme 3.0 ou 3.01 ou 3.001 ou 3.00000000002 ou 4.124578521…). De même, nos types énumérés sont discrets, mais les types structurés ne sont pas acceptés comme discrets !

Autre souci : c'était pas plus simple d'utiliser IF/ELSIF plutôt que des CASE imbriqués ?

Vous vous doutez bien que non ! Les conditions sont strictes lorsque l'on construit un type polymorphe. Pas de IF, ne pas faire de répétition dans l'instruction WHEN (ne pas répéter la composante lv1 par exemple), un seul CASE et c'est fini (d'où l'obligation de les imbriquer plutôt que d'en écrire deux distincts)… C'est qu'on ne ferait plus ce que l'on veut ! :p

Des objets polymorphes

Maintenant que nous avons déclaré nos types, il faut déclarer nos variables :

1
2
3
4
Kevin : T_Eleve := ("DUPONT              ","Kevin               ",PRIMAIRE) ; 
Laura : T_Eleve := ("DUPUIS              ","Laura               ",LYCEE) ; 
Bltn_Kevin : T_Bulletin(Kevin.classe) ; 
Bltn_Laura : T_Bulletin(Laura.classe) ;

Vous devriez avoir remarqué deux choses :

  • Il faut fournir un paramètre quand on définit Bltn_Kevin et Bltn_Laura, sans quoi le compilateur échouera et vous demandera de préciser votre pensée.
  • Par conséquent, lorsque vous déclarez les variables Kevin et Laura, vous devez les avoir initialisées (ou tout du moins avoir initialisé Kevin.classe et Laura.classe).

Nous avons ainsi obtenu deux variables composites Bltn_Kevin et Bltn_Laura qui n'ont pas la même structure. Bltn_Kevin a comme composantes :

  • Bltn_Kevin.classe : accessible seulement en lecture, pour des tests par exemple.
  • Bltn_Kevin.Math, Bltn_Kevin.Francais et Bltn_Kevin.sport : accessibles en lecture et en écriture.

Quant à elle, la variable Bltn_Laura a davantage de composantes :

  • Bltn_Laura.classe : en lecture seulement
  • Bltn_Laura.Math, Bltn_Laura.francais, Bltn_Laura.sport : en lecture et écriture (comme Bltn_Kevin)
  • Bltn_Laura.lv1, Bltn_Laura.lv2, Bltn_Laura.bio, Bltn_Laura.chimie : en lecture et écriture.

Le paramètre Bltn_Laura.classe se comporte comme une composante mais n'est pas modifiable ! Impossible d'écrire par la suite Bltn_Laura.classe := PRIMAIRE ! ! !

Il est ensuite possible de créer des sous-types (pour éviter des erreurs par exemple) :

1
2
3
type T_Bulletin_Primaire is T_Bulletin(PRIMAIRE) ; 
type T_Bulletin_College is T_Bulletin(COLLEGE) ; 
type T_Bulletin_Lycee is T_Bulletin(LYCEE) ;

Les types structurés mutants

Comment peut-on muter ?

Je vous arrête tout de suite : il ne s'agit pas de monstres ou de types avec des pouvoirs spéciaux ! Non, un type mutant est simplement un type structuré qui peut muter, c'est-à-dire changer. Je vais pour cela reprendre mon type T_Bulletin. Attention ça va aller très vite :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
type T_Bulletin(classe : T_Classe := PRIMAIRE) is
record
   --PARTIE FIXE

   francais, math, sport : float ;    --ou float range 0.0..20.0 ; 

   --PARTIE VARIABLE : COLLEGE ET LYCEE

   case classe is
      when PRIMAIRE =>
         null ; 
      when COLLEGE | LYCEE => 
         lv1, bio : float ; 

   --PARTIE SPECIFIQUE AU LYCEE

         case classe is
            when LYCEE => 
               chimie, lv2 : float ; 
            when others =>
               null ; 
         end case ; 
   end case ; 
end record ;

Euh… T'aurais pas oublier de faire une modification après ton copier-coller ?

Non, regardez bien mon paramètre classe tout en haut : il a désormais une valeur par défaut : PRIMAIRE ! Vous allez me dire que ça ne change pas grand chose, et pourtant si ! Ainsi vous allez pouvoir écrire :

1
2
Eric : T_Eleve ;             -- non prédéfini
Bltn_Eric : T_Bulletin ;     -- pas la peine d'indiquer un paramètre, il est déjà prédéfini

Et désormais, Bltn_Eric est un type mutant, il va pouvoir changer de structure en cours d'algorithme ! Nous pourrons ainsi écrire :

1
2
3
4
5
6
7
...
   Eric : T_Eleve ;
   Bltn_Eric : T_Bulletin ; 
BEGIN
   Bltn_Eric := (COLLEGE,12.5,13.0,9.5,15.0,8.0) ; 
   Bltn_Eric := Bltn_Kevin ;                         --On suppose bien-sûr que Bltn_Kevin est déjà "prérempli"
...

Par contre, comme le fait d'attribuer une classe à Bltn_Eric va modifier toute sa structure, il est interdit de noter uniquement « Bltn_Eric.classe := COLLEGE ; »

Toute changement de la composante classe doit se faire de manière «globale», c'est-à-dire «tout en une seule fois» ! Autre contrainte, si vous déclarez votre objet Bltn_Eric de la manière suivante :

1
Bltn_Eric : T_Bulletin(COLLEGE) ;

Alors votre objet ne sera plus mutable ! On revient au cas d'un simple type structuré polymorphe. Donc ne spécifiez pas la classe durant la déclaration si vous souhaitez pouvoir changer la structure.

Toujours plus loin !

Pour aller plus loin il serait judicieux de «fusionner» les types T_Eleve et T_Bulletin de la manière suivante :

 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
type T_Bulletin(classe : T_Classe := PRIMAIRE) is                      --!!! POUR L'INSTANT, PAS DE CHANGEMENTS !!!
record
   --PARTIE FIXE

   francais, math, sport : float ;    --ou float range 0.0..20.0 ; 

   --PARTIE VARIABLE : COLLEGE ET LYCEE

   case classe is
      when PRIMAIRE =>
         null ; 
      when COLLEGE | LYCEE => 
         lv1, bio : float ; 

   --PARTIE SPECIFIQUE AU LYCEE

         case classe is
            when LYCEE => 
               chimie, lv2 : float ; 
            when others =>
               null ; 
         end case ; 
   end case ; 
end record ;

type T_Eleve is                            --!!! LA MODIFICATION SE FAIT ICI !!!
record
   nom : string(1..20) ; 
   prenom : string(1..20) ;
   bulletin : T_Bulletin ; 
end record ;

Inutile de garder deux types : le type T_Bulletin sera intégré au type T_Eleve. En revanche, il faudra que le type T_Eleve soit déclaré après le type T_Bulletin. Si cela se révélait impossible, vous pouvez toujours éviter ce problème en écrivant la spécification de T_Eleve, puis en décrivant T_Bulletin et enfin T_Eleve :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
type T_Eleve ;                         -- !!! AJOUT D'UNE SPECIFICATION !!! Le reste ne change pas.


type T_Bulletin(classe : T_Classe := PRIMAIRE) is
record
   ... 
end record ;


type T_Eleve is
record
   ... 
end record ;

Plus besoin non plus de garder une composante classe au sein du type T_Eleve puisqu'elle existe déjà dans la composante bulletin !

Vous voilà désormais armés pour créer vos propres types (modulaires, énumérés, sous-types, structurés [non paramétré, polymorphe ou mutant]). Combiné à l'usage des tableaux, voilà qui élargit vraiment nos horizons et nos possibilités. Si nous développions notre type T_Eleve et les fonctionnalités qui s'y rapportent, nous pourrions créer un package volumineux et complet permettant d'établir une sorte de base de données des élèves d'un établissement : création et modification d'un «fichier élève» enregistrant de nombreuse variables de type T_Eleve, le numéro de téléphone, l'adresse, les notes par matière (dans des tableaux inclus dans le type T_Eleve)… la seule limite est votre imagination et votre temps libre.

D'ailleurs, nous allons réaliser ce genre de programme dans le prochain chapitre : il s'agira de créer un logiciel gérant, non pas des élèves, mais votre collection de CD, DVD… Comme vous l'avez deviné, notre prochain chapitre ne sera pas théorique, ce sera un TP ! Alors, si certains points vous posent encore problème, n'hésitez pas à les revoir.


En résumé :

  • Un sous-type ou SUBTYPE, est une restriction d'un type préexistant. Par conséquent, les sous-types bénéficient de toutes les fonctionnalités offertes au type initial.
  • Préférez créer un type énuméré plutôt que de représenter certaines propriétés par des nombres. Il est plus simple de comprendre qu'une personne est de sexe masculin que de comprendre que son sexe est le n°0.
  • Les types articles ou structurés sont créées à l'aide du mot-clé RECORD. Contrairement aux tableaux, ils permettent de rassembler de nombreuses informations de types très divers dans des composantes. Ces composantes peuvent être elles-mêmes des types articles.
  • Les types articles peuvent être paramétrés à la manière des fonctions. Ce paramétrage permet de créer des types polymorphes, c'est à dire qu'une partie de la structure du type dépendra du paramètre fourni. Lorsqu'un variable de type polymorphe est déclarée, son type doit absolument être paramétré.
  • En fournissant une valeur par défaut au paramètre, vous pouvez définir un type mutant dont la structure pourra évoluer au cours de votre code. Plus aucun paramétrage n'est alors nécessaire à la déclaration de votre variable.