Si le téléchargement ne démarre pas apràs 5 secondes, veuillez cliquer sur le lien "Télécharger"
Patrick Bellot1 Akim Demaille2
1ENST - Ecole Nationale Supérieure des Télécommunications´
2EPITA - Ecole Pour l’Informatique et les Techniques Avancées´
June 14, 2012
Objets, Classes, Héritage
Le Système Eiffel
un langage de haut niveau con¸cu pour le Génie Logiciel, portable, avec une syntaxe originale et claire une clarification et une conception moderne de l’héritage multiple de classes l’essentiel des outils programmatiques de très haut niveau
(classes virtuelles, génériques, exceptions, etc.) de nombreuses bibliothèques
EiffelCOM (COM,OLE,ActiveX),
EiffelCORBA,
EiffelMath,
EiffelNet (client-serveur), EiffelLex & EiffelParse,
EiffelStore (BD),
EiffelWEB,
Eiffel DLE (dynamic link),
EiffelVision (GUI),
Graphical Eiffel for Windows, Eiffel WEL (Windows), EiffelThreads, etc.
clauses d’adaptation d’héritage résoudre les “problèmes” de l’héritage multiple ;
programmation contractuelle favoriser la réutilisabilité et la modularité ;
interface graphique fonctionnement de l’interface, drag and drop, etc. originales mais pas toujours appréciées.
Compilateur à trois modes dont un mode incrémental très utile en phase de développement
EiffelBench un environnement de développement en accord avec le langage
EiffelBuild un environnement de développement d’interfaces graphiques
EiffelCase un environnement de conception d’applications
Objets, Classes, Héritage
Classes et Objets
L’héritage de Classes
Le Système Eiffel
Objets, Classes, Héritage
Classes et Objets
L’héritage de Classes
Le Système Eiffel
Tous les programmeurs ont en tête la célèbre équation venue de la programmation structurée :
Structures de données + Algorithmes = Programmes
L’objet prolonge cette équation :
Vue Logicielle Un objet est la réunion d’un type de données et des programmes permettant de traiter ces données. L’outil de sa déclaration est la classe.
Vue Logique Un objet est la représentation auto-suffisante d’un concept servant à la résolution d’un problème. La classe est la description d’une catégorie d’objet.
L’analyse d’un problème à traiter en orienté objet est avant tout une analyse orientée données.
Pour chaque donnée du problème, on décrit ses propriétés (attributs) et ses comportements (routines). Ce peut être des données réelles ou abstraites.
Puis ces données sont utilisées pour résoudre le problème posé.
? Très différent de la démarche algorithmique.
La classe est la déclaration (type) d’un objet. Un objet est une instance d’une classe. La classe possède deux composantes :
attributs la composante statique, les données contenues dans l’objet
routines la composante dynamique, les procédures ou fonctions attachées à l’objet.
Attributs et routines sont globalement appelés les primitives de la classe dans la terminologie Eiffel.
class POINT
-- un point dans un dessin ge´ométrique feature
-- deux attributs : les coordonnées xc,yc : INTEGER ;
-- une méthode : changer les coordonnées set_x_y(x,y : INTEGER) is
do
xc := x ; yc := y ;
end ; end -- class POINT
variable
recevant nom le résultat
de paramètres éventuel méthode de la méthode
objet message
recevant le message
L’objet exécute la méthode m qu’il connaˆ?t. Il l’exécute dans son propre contexte. C’est toujours un objet qui exécute la méthode.
Dans le cas d’objets
distribués, il s’agit réellement d’un envoi de message. Sinon, c’est un appel de procédure ou de fonction.
p : POINT ;
do ...
variable de type référence
... end ;
Sauf déclaration explicite, toutes les variables d’objets sont des variables de type référence. Elles contiennent des pointeurs.
class POINT
-- un point dans un dessin ge´ométrique create make -- routine d’initialisation
feature
-- une méthode d’initialisation make(x,y : INTEGER) is do set_x_y(x,y) ;
end ;
-- suite inchangée -- ...
end -- class POINT
Les attributs sont initialisés avec des valeurs par défaut (e.g., 0 pour un entier, Void pour une variable de type référence).
Si l’on désire initialiser les objets à la création, il faut une routine d’initialisation.
Puis on crée l’objet en faisant appel à sa routine d’initialisation :
p : POINT ;
create p.make(23,64) ;; -- crée un point initialise´
Il peut y avoir plusieurs routines d’initialisation pour une même classe d’objets. C’est à la création que l’on choisit laquelle utiliser.
Lorsqu’au moins une routine d’initialisation est déclarée, il n’est plus possible de créer un objet sans faire appel à l’une de ces routines. ? Sécurité
Lecture Par défaut, les attributs sont accessibles en lecture en utilisant la notation pointée : sauf déclaration contraire, tout objet peut connaˆ?tre la valeur d’un attribut d’un autre objet.
Ecriture´ Les attributs ne sont jamais accessibles en écriture sauf pour l’objet lui-même. Pour les autres objets, il faut que l’objet à modifier fournisse une routine de modification comme la routine set x y(x,y : INTEGER) de la classe POINT.
? Sécurité
Par défaut, les routines, procédures et fonctions, sont accessibles en exécution.
Pour les routines comme pour les attributs, il est possible d’en restreindre l’accès aux objets de certaines classes.
Restriction des accès feature ou feature{ANY} primitives avec accès par défaut (tous les objets sont de la classe ANY)
feature{A,B,C,...} primitives accessibles seulement par les objets des classes A, B, C
feature{} ou feature{NONE} primitives inaccessibles (NONE : classe sans instance)
Une syntaxe claire à la Pascal avec des mots clés explicites plutôt que des symboles.
Initialisation sécurisée par les routines de création.
Encapsulation et protection des données (attributs) :
interdit en écriture ; modulation des accès par la clause feature.
Objets, Classes, Héritage
Classes et Objets
L’héritage de Classes
Le Système Eiffel
Ainsi présentées, les classes ne sont que des modules un peu
évolués. L’héritage fait la force logicielle des langages à objets.
Quand on définit une classe, on peut déclarer qu’elle hérite d’une ou plusieurs autres classes. La classe qui hérite est appelée classe fille ou sous-classe de la classe dont elle hérite. Cette dernière est appelée classe mère ou surclasse.
? La classe fille hérite de toutes les primitives accessibles définies dans la classe mère. Puis elle est spécialisée par le programmeur.
Une fois héritées les primitives accessibles de la classe mère, il existe deux manières non exclusives de spécialiser la classe fille :
l’enrichissement de nouvelles primitives sont ajoutées à la classe fille ; Eiffel parle alors d’enrichissement modulaire ;
la substitution des primitives héritées sont redéfinies et adaptées pour la classe fille ; Eiffel parle alors d’affinage.
class SQUARE create make feature
xc,yc : INTEGER ; -- coordonnées width : INTEGER ; -- largeur
make(x,y,l : INTEGER) is
do
xc := x ; yc := y ; width := l ;
end ;
area : INTEGER is do
Result := width*width ; end ; end -- class SQUARE
class RECTANGLE inherit SQUARE rename make as make_square redefine area end ; create make feature -- EXTENSION height : INTEGER ; -- hauteur make(x,y,w,h : INTEGER) is do
make_square(x,y,w) ; height := h
end ; -- AFFINAGE area : INTEGER is do
Result := width * height ; end ; end -- class RECTANGLE
L’héritage est simple si toute classe n’hérite au plus que d’une seule classe. Sinon il est multiple.
L’héritage multiple est bien plus puissant que l’héritage simple mais il pose des
problèmes qu’Eiffel résout grâce à la clause d’adaptation d’héritage
Class A Class B
primitive p | primitive p |
Que signifie alors
Class C
inherit A B |
X.p?
Un objet a comme type toute ses surclasses. Supposons l’héritage :
SHAPE
POLYGON
SQUARE
alors les affectations suivantes sont valides :
s : SHAPE ; p : POLYGON ; q : SQUARE ; r : RECTANGLE ;
do
s := p ; p := q ; q := r ;
end ;
Modéliser et catégoriser les objets du problème que l’on traite informatiquement : des classes générales (classes mères) sont dérivées en classes spécialisées (classes filles) :
Dans cet optique, l’héritage multiple est rarement nécessaire.
Il reste cependant bien plus pratique de disposer de l’héritage multiple :
Est-ce valide :
q : SQUARE ; r : RECTANGLE ;
do Valide, car RECTANGLE hérite de
... SQUARE!
q := r ;
... end ;
Est-ce sain ? Non, car un rectangle n’est pas un carré !
? “hérite de” = “est une sorte de” alors que nous avons appliqué : ? “hérite de” = “est une extension de”
q : SQUARE ; r : RECTANGLE ; do
...
q := r ;
... end ;
Et si nous faisons hériter SQUARE de RECTANGLE ? En appliquant :
? “hérite de ¿¿ = ¡¡ est une spécialisation de”
C¸a ne marche pas non plus nous apprend la programmation contractuelle car on peut changer une dimension d’un rectangle sans changer l’autre.
Une mauvaise classe SEGMENT
class SEGMENT inherit
POINT La bonne classe SEGMENT :
feature class SEGMENT extremite : POINT ; inherit
... SHAPE_WITHOUT_AREA
end -- class SEGMENT feature first, second : POINT ;
Car elle autorise : ...
p : POINT ; end -- class SEGMENT s : SEGMENT ;
...
p := s ;
L’héritage sert à la factorisation du code.
L’héritage multiple est nécessaire.
Un formidable outil de réutilisation du code.
class BLUE_BUTTON
inherit
PUSH_B -- EiffelVision rename make as old_make end ;
COLORS -- EiffelVision create make feature
make(t : STRING; w:WINDOW; x,y,dx,dy : INTEGER) is do old_make(t,w) ; set_x_y(x,y) ; set_size(dx,dy) ; set_color(blue) ;
end ; end -- class BLUE_BUTTON
déccrocher() | décrocher() |
Pour la réutilisation aussi, l’héritage multiple est nécessaire et préférable :
Prenons le graphe d’héritage suivant :
|
|
RECTANGLE
draw(g : GRAPHICS)
SQUARE c : RECTANGLE, que dire de
draw(g : GRAPHICS) | |
redefine draw |
c.draw(g) ?
Il est clair que c’est la méthode draw de la classe RECTANGLE qui est appelée. r : SQUARE que dire de r.draw(g) ? Les choses sont moins claires car r peut
RECTANGLE contenir un RECTANGLE ou un SQUARE et
draw(g : GRAPHICS) il serait logique de se baser sur la classe de l’instance pour déterminer la méthode draw à utiliser.
Si l’on se base sur le type de la variable
? recherche statique de méthode
La méthode utilisée est alors déterminée à la compilation.
Si l’on se base sur le type de l’objet
? recherche dynamique de méthode
La méthode utilisée est déterminée dynamiquement au moment de l’exécution de programme.
Eiffel pratique toujours la recherche dynamique de méthode mais aussi d’attributs
Prenons une variable f : SHAPE, que peut-on dire de f.draw(g) ?
SHAPE
Si f contient une instance de SQUARE, appeler draw de
SQUARE CIRCLE cette classe.
|
|
Si f contient une instance de CIRCLE, appeler draw de
RECTANGLE cette classe.
draw(g : GRAPHICS)Etc.
Comment être suˆr que tous les objets ayant le type SHAPE ont une méthode draw ?
deferred class SHAPE
feature -- common to all the figures. xc,yc : INTEGER ;
set_x_y(x,y : INTEGER) is do
xc := x ; yc := y ;
end ;
feature -- to implement in subclasses draw(g : GRAPHICS) is deferred end ; end -- class SHAPE
Une méthode retardée (ou virtuelle ou abstraite) doit être mise en œuvre dans toutes les sous-classes.
SHAPE Si f : SHAPE contient une
instance de la classe SHAPE alors f.draw(g) n’est pas
SQUARE CIRCLE définie.
|
|
Si l’on con¸coit bien ce qu’est une instance de la classe
SQUARE ou CIRCLE, on voit
RECTANGLE bien qu’une instance de la
draw(g : GRAPHICS)
classe SHAPE n’a aucun sens.
deferred class SHAPE
Une classe retardée ne
feature -- common to all the figures.
xc,yc : INTEGER ; peut être instanciée ni même avoir de
set_x_y(x,y : INTEGER) is do routine de création.
xc := x ;
Une classe qui
yc := y ;
end ; contient des méthodes retardées
feature -- to implement in subclasses draw(g : GRAPHICS) is deferred end ; doit être retardée.
? Sécurité du code
end -- class SHAPE
Code classique
f : ARRAY[1..N] of SHAPE ; for i:=1 to N do
case type(f(i)) of Code objet
TYPE_SQUARE : f : ARRAY[1..N] of SHAPE ;
begin ... end; for i:=1 to N do f.draw(g) ; TYPE_CIRCLE : begin ... end;
.... ? taille ne dépend pas du
end ; nombre de types
? granularité fine du code
? taille du code dépendant du nombre de types
? difficilement extensible
Pour ajouter un nouveau type de figure, on ajoute une classe.
class OVAL
inherit
SHAPE feature
draw(g : GRAPHICS) is do
... end ;
... end ;
? on ne touche pas à l’existant
? le compilateur vérifie les oublis
Héritage multiple : ? Modélisation
? Factorisation
? Réutilisation
Mais problèmes de l’héritage multiple...
Recherche dynamique de méthodes et d’attributs :
? Souplesse
? Granularité du code
Mais certains problèmes de compilation...
Classes et méthodes retardées :
? Suˆreté du code
? Extensibilité
Objets, Classes, Héritage
Le Système Eiffel
Les Applications Eiffel
Les Interfaces
Objets, Classes, Héritage
Le Système Eiffel
Les Applications Eiffel
Les Interfaces
Une application Eiffel est appelée un système. Elle est composée de :
classes :
une par fichier (.e) regroupées en clusters l’une d’elle est la classe principale
bibliothèques Eiffel (une seule en fait) bibliothèques externes
un fichier de description de l’application fichier LACE, Langage pour l’assemblage des classes en Eiffel
Point de vue LOGIQUE C’est un ensemble de classes formant une partie auto-suffisante de l’application.
Point de vue PHYSIQUE Ces classes sont dans un même
répertoire.
Point de vue LACE Un cluster est un nom associé à un répertoire.
system geo
root
TEST(TEST): "main"
default precompiled("$EIFFEL3/precomp/spec/$PLATFORM/base")
cluster
TEST: "$EIFFELDIR/TEST" ;
FIGS: "$EIFFELDIR/FIGURES" ;
external object: "$(EIFFEL3)/library/lex/spec/$(PLATFORM)/lib/lex.a" end
FINALIZING réalise toutes les optimisations possibles en terme de vitesse d’exécution et d’occupation mémoire. Produit un exécutable. Très long !
FREEZING compile et produit un exécutable.
MELTING compilation incrémentale par ajout de patch dans le
code. Très rapide : une modification n’entraˆ?ne que la recompilation de ce qu’il est vraiment nécessaire mais les performances résultantes ne sont pas très bonnes. Utile en cours de développement.
Objets, Classes, Héritage
Le Système Eiffel
Les Applications Eiffel
Les Interfaces
Quels sont les objets manipulés par l’interface ?
A chaque objet doit correspondre une classe décrivant l’objet` lui-même et des routines réalisant les opérations qu’on peut lui appliquer.
L’utilisateur doit voir sur son écran des représentations graphiques explicites des objets de l’application.
Il doit pouvoir réaliser des opérations sur ces objets en manipulant ces leurs représentations graphiques.
Pour l’utilisateur de l’interface, l’objet doit être sa représentation graphique.
L’utilisateur doit pouvoir sélectionner tous les objets de l’interface et leurs appliquer toutes les opérations sémantiquement valides pour ces objets.
Le type d’un objet de l’interface doit être graphiquement explicite.
L’interface doit mettre en évidence les opérations valides et invalides en utilisant des techniques d’interaction favorisant la prévention de l’erreur plutôt que sa détection a posteriori.
¡¡ Mieux vaut prévenir que guérir ¿¿ s’applique aux erreurs de type mais aussi à toutes les sortes d’erreurs.
L’interface doit avertir et guider plutôt que corriger.
Elle doit proposer des solutions alternatives permettant de transformer une opération erronée en opération valide.
Très critiqué
Saisie d’un objet : cliquer avec le troisième bouton et relâcher. Une représentation iconique de l’objet est attachée au pointeur de la souris.
Transport de l’objet : en déplac¸ant le pointeur de la souris. Un trait relie en permanence l’objet au pointeur de souris.
Largage de l’objet : cliquer avec le troisième bouton et relâcher le bouton, puis action ou rédemption.
Abandon du drag and drop : cliquer avec un autre bouton et relâcher ce bouton.
Ce sont des genres de boutons représentant des fonctions comme
Editer´ , Détruire, etc.
Drag and droper un objet dans son accessoire d’édition permet de l’éditer.
En pratique, cela donne quoi ?
L’environnement EiffelBench est une concrétisation de ces principes.
Absence de menus.
Absence de bouton texte.
Tout est graphique.
C’est général !
Chaque type d’objet a sa représentation iconifiée :
les classes ; les routines ; les objets ; les codes d’erreur ; etc.
Les accessoires récepteurs sont dessinés en fonction de le représentation iconifiée des objets qu’ils peuvent recevoir. On retrouve la même chose dans EiffelBuild, le générateur d’interfaces.
On n’aime ou on n’aime pas car c’est ¡¡différent¿¿...
Quelques bons principes mais un apprentissage est nécessaire.
Très loin de faire l’unanimité !