Les objets en Ocaml : constructions du langage
Heritage´
En programmation objet, on appele heritage la re-utilisation d’attributs d’une classe existante pour en deriver une nou-velle de maniere incrementale. La classe existante est appel´ee classe parente ou super-classe, alors que la nouvelle est dite deriv´ee´ ou sous-classe. La classe d´eriv´ee, comme toute classe, d´ecrit le comport´ement des objets futurs, mais cette description est incr´ementale: on ne donne que les modifications ou extensions par rapport aux comport´ements et donn´ees de la super-classe. Les variables et m´ethodes de la super-classe sont implicitement pr´esentes dans la sous-classe. Pendant l’h´eritage, il est possible d’ajouter des nouvelles d´efinitions de variables ou de m´ethodes dans la sous-classe, ou au contraire, de fournir du code diff´erent pour une m´ethode en provenance de la super-classe. Tous les attributs repris dans la sous-classe sans modification sont dit herit´es´ de la super-classe; toutes les m´ethodes chang´ees dans la sous-classe sont dites re-definies´.
L’h´eritage entre classes se traduit pas le partage d’attributs. La sous-classe partage avec sa super-classe le code, pour les m´ethodes h´erit´ees, et les valeurs initiales, pour les variables.
Heritage´ simple
La classe compte contient une variable avec le solde d’un compte, et des m´ethodes pour consulter le solde, pour virer et pour retirer de l’argent.
class compte s_init =
object
val mutable solde = s_init
method depot n = solde <- solde + n
method retrait n = solde <- solde – n
method donne_solde = solde
method affiche_solde =
begin print_string »Votre solde est: « ;
print_int solde; print_newline ()
end
end;;
class compte :
int ->
object
val mutable solde : int
method affiche_solde : unit -> unit
method depot : int -> unit
method donne_solde : int
method retrait : int -> unit
end
parties suivantes. La classe compteremunere est une sous-classe de compte. Elle h´erite de tous les attributs de compte, et les etend´ par la d´efinition de deux nouvelles variables taux et interets, et d’une nouvelle m´ethode ajoute interets.
class compteremunere s_init =
object
inherit compte s_init
val taux = 5 (* Donne en % *)
val mutable interets = 0
method ajout_interets =
begin interets <- (solde*taux)/100;
solde <- solde + interets
end
end;;
compteremunere poss`ede toutes les variables et m´ethodes de sa classe parente. Ceci se refl`ete dans l’interface inf´er´ee par le typeur:
class compteremunere :
int ->
object
val mutable interets : int
val mutable solde : int
val taux : int
method affiche_solde : unit
method ajout_interets : unit
method depot : int -> unit
method donne_solde : int
method retrait : int -> unit
end
On peut utiliser toutes les m´ethodes, qu’elles soient nouvelles ou h´erit´ees:
# let dupont = new compteremunere 200;; val dupont : compteremunere = <obj>
# dupont#depot(1000);;
– : unit = ()
# dupont#ajout_interets;; – : unit = ()
# dupont#affiche_solde;; Votre solde est: 1260
– : unit = ()
Re-definition´
Le code pre-existant n’est pas toujours adapt´ee a` tous les contextes de re-utilisation. Il arrive alors que l’on m´elange h´eritage et re-d´efinition des m´ethodes pour d´ecrire, en particulier, du code plus sp´ecialis´e dans une sous-classe. Une nouvelle version de la sous-classe compteremunere re-d´efinit la m´ethode d’affichage pour signaler la part des interˆets dans le solde du compte.
class compteremunere s_init =
object
inherit compte s_init
val taux = 5 (* Donne en % *)
val mutable interets = 0
method ajout_interets =
begin interets <- (solde*taux)/100;
solde <- solde + interets
end
method affiche_solde =
begin print_string »Votre solde est: « ;
print_int solde; print_newline ();
print_string » dont la part d’interets est: « ;
print_int interets
end
end;;
L’exemple suivant montre le nouveau comport´ement de l’affichage:
# let compterem1 = new compteremunere 200;; val compterem1 : compteremunere = <obj>
# compterem1#ajout_interets;;
– : unit = ()
# compterem1#affiche_solde;; Votre solde est: 210
dont la part d’interets est: 10 – : unit = ()
En Ocaml, il existe une contrainte (que nous justifierons plus tard) a` la re-d´efinition d’une m´ethode pendant l’heritage´: le type de la m´ethode d´eriv´ee doit etreˆ egal´ ou plus sp´ecialis´e (du point de vue du polymorphisme3) que celui de la m´ethode parente. Intuitivement, cette contrainte n’est pas surprenante: la sp´ecialisation (´eventuelle) du type et du comport´ement d’une m´ethode est coh´erente avec la notion mˆeme de sp´ecialisation d’une classe par h´eritage. Dans notre exemple, le type de affiche solde reste inchang´e, et de ce fait, la re-d´efinition de la m´ethode dans la nouvelle classe est valid´ee par le typeur:
3 Ici, il s’agit de polymorphisme param´etrique. Nous pr´eciserons cette notion dans la partie du cours d´edi´ee au typage.
class compteremunere :
int ->
object
val mutable interets : int
val mutable solde : int
val taux : int
method affiche_solde : unit
method ajout_interets : unit
method donne_solde : int
method depot : int -> unit
method retrait : int -> unit
end
Auto-ref´erencement:´ self
L’auto-r´ef´erencement est la possibilit´e pour une m´ethode d’utiliser les autres m´ethodes (ou m´ethodes soeurs) qui se trouvent dans un objet. Consid´erons une autre version de la classe cell o`u l’on incorpore la nouvelle m´ethode double. Dans cette derni`ere, on utilise la m´ethode set de la mˆeme classe4.
class cell =
object(self)
val mutable cont = 0
method get = cont
method set n = cont <- n
method double = self#set (cont*2)
method print = print_int self#get
end;;
Nous ne pouvons pas employer set qu’au moyen d’un envoi de message. Or, au moment de d´efinir la classe, il n’y a pas encore d’objet qui en soit une instance! En revanche, une invocation future de double se fait par o#double, via un certain objet o, dit objet courant. Quoi de plus normal que d’aller chercher, pendant l’execution´ de double, le code de sa m´ethode soeur dans cet objet? On utilise souvent le mot self pour parler de l’objet courant. En Ocaml, lors de la d´efinition d’un classe, on peut donner un nom quelconque a` cet objet, au moyen de la construction object(ident). Nous pref´erons utiliser la terminologie objet et l’appelons toujours self. Dans notre exemple, la m´ethode d’affichage utilise egalement´ une m´ethode soeur (get) via self.
# let c = new cell;; val c : cell = <obj>
# c#set 1;;
– : unit = ()
# c#print;;
1- : unit = ()
# c#double;; – : unit = ()
# c#get;;
– : int = 2
# c#print;;
2- : unit = ()
4 Plus pr´ecisement, on peut utiliser toute m´ethode soeur s qui se trouve dans la hi´erarchie de cette classe: soit dans une super-classe, si s est h´erit´e, soit dans une sous-classe, si s est red´efinie plus tard dans la classe effective´ de l’objet auquel on envoit le message. Ce point est explor´e en d´etail dans la partie d´edi´e a` la liaison tardive.
De mani`ere g´en´erale, il est pratique d’avoir acc`es aux m´ethodes soeurs d’une classe, mais l`a o`u r´eside la veritable puissance de l’auto-ref´erencement c’est dans son caract`ere dynamique: l’objet ref´erenc´e par self n’est pas d’une classe fig´ee. Dans notre exemple, il peut etreˆ de classe cell ou d’une de ses sous-classe. Ceci implique que le comport´ement des m´ethodes invoqu´ees avec self peut varier dynamiquement (selon la classe effective de self). Il s’agit l`a d’un probl`eme de liaison des variables (ici les m´ethodes), que nous etudierons´ un peu plus loin dans la partie dedi´ee a` la liaison tardive.
Referencement aux classes parentes: super
C’est la possibilit´e pour une classe d’invoquer une m´ethode de la classe parente. Cela est utile dans un contexte de red´efinition de la m´ethode invoqu´ee (autrement, self est suffisant). En Ocaml, pour employer une m´ethode d’une super-classe, on doit n´ecessairement nommer cette derni`ere. Le but est d’identifier la classe d’origine de la m´ethode demand´ee, ce qui est justifi´e par l’ambig¨uit´e que peut entrainer l’h´eritage multiple. On donne un nom a` la super-classe par la construction inherit C a1 … an as nom superclasse. Par la suite, toute utilisation de la m´ethode de la super-classe se fait par envoi de message via ce nom-l`a. La classe backupCell d´efinit des cellules dont on garde une copie apr`es modification. Elle re-d´efinit la m´ethode set h´erit´ee de cell, et utilise via le nom super le code de cette m´ethode avant re-d´efinition.