conception orientée objet
TRANSCRIPT
Conception Orientée Objet
Laurent Henocquehttp://laurent.henocque.free.fr/
Enseignant Chercheur ESIL/INFO Francehttp://laurent.henocque.perso.esil.univmed.fr/
mis à jour en Décembre 2008
Licence Creative Commons
Cette création est mise à disposition selon le ContratPaternité-Partage des Conditions Initiales àl'Identique 2.0 France disponible en ligne
http://creativecommons.org/licenses/by-sa/2.0/fr/
ou par courrier postal à Creative Commons, 559Nathan Abbott Way, Stanford, California 94305,USA.
Objectifs
• Donner une compréhension des enjeux de laconception orientée objet, et desconnaissances actuelles sur le sujet
Plan
• Panorama du concept d'objet en Informatique• Principes généraux de détermination des classes• Principes de l'utilisation des objets• A propos de réutilisabilité• Conception des interfaces de programmation en
C++• Types de classes
Panorama du concept d'objet
Un modèle de la réalité
• les mêmes réactions que la réalitéreprésentée,
• la même modularité que le monde réel.
Modèle de la réalité (2)
Chaque objet se comporte "comme" sonhomologue réel en termes de :
• persistance de son état• réactions aux perturbations externes• communication avec les autres objets
Persistance
• La persistance d’un état est obtenue de façonélémentaire : par stockage de donnéespertinentes dans une structure (les types structen C et record en Pascal).
• La structure est donc l'élément fondamental dansla représentation informatique de l'objet : unespace clos et contigu où figurent toutes lesinformations relatives à un objet.
Encapsulation des données
• La démarche consistant à décrire un telespace est nommée encapsulation desdonnées.
• Une structure est également décrite commeun agrégat de données hétérogènes.– Le type tableau au contraire constitue un
agrégat de données d’un même type(homogène).
Réactions
• La réaction aux perturbations externes estsimulée par des fonctions que l'on peut appliquerà l'objet.
– Message -> fonction– Evénement -> fonction
Difficultés
• L'existence chez différents objets defonctionnalités sémantiquement voisines, et pourautant différentes dans leur réalisation justifie lepolymorphisme
• Le nommage des fonctions globales nécessite dedonner des noms différents à des fonctionshomologues, et donc à multiplier dans unprogramme le nombre des symboles.
Difficultés
• Les fonctions globales ne permettent pasavec une grande finesse le contrôled'éventuelles restrictions au graphe d'appeldu programme.
• L’organisation en classe permet de finementdéfinir les autorisations(public/protected/private)
Polymorphisme
• On accepte de donner le même nom à desfonctions dont les arguments diffèrent.
• Ainsi, un programmeur peut utiliser un nomunique pour l'associer à un conceptd'opération unique, utilisable dans différentscontextes
Avantages du polymorphisme
• Les programmes gagnent en abstraction• Il n'est pas nécessaire de définir une règle
de nommage des fonctions homologues(par exemple "bouge_caillou", ou bien"CaillouBouge").
• L'écriture des programmes est facilitée.
Encapsulation de fonctions
• Dans ce modèle, sont associées logiquement à lastructure (de données) représentant un objet lesseules fonctions qui simulent (ou implantent) desréactions de cet objet au monde extérieur.
• Ces fonctions sont alors appelées des méthodes.• La déclaration d'une méthode omet la mention du
paramètre désignant l'objet auquel elle s'applique,appelé « support »
Opérations et Méthodes
• Par définition, toutes les méthodes de mêmenom constituent des implantationsappropriées du même concept, qui porte lenom d' opération.
• Chaque méthode d'une classe implante uneopération donnée pour cette classe
Polymorphisme et méthodes
• Aucun langage de programmation nepermet de distinguer deux fonctions par leseul type de leur valeur de retour.
Objets
• Une structure qui encapsule à la fois desdonnées et des méthodes est appelée unobjet.– A ce titre, certaines bases de données dites
"objet" n'en méritent pas le nom puisqu'elles nesont qu'orientées "structure".
• Notons que le terme d'objet est défini sansqu'il ne soit question d'héritage.
Communication
• L'envoi d'un message d'un objet à un autresuppose qu'une méthode du premier appelle uneméthode du deuxième.
• Dans le cas "synchrone", la réponse est fournie parla valeur de retour de la fonction appelée
• Dans le cas asynchrone, le réponse est fournie parun message en retour: la méthode appelante peutterminer avant que la réponse ne parvienne
Messages / Méthodes
• L'encapsulation permet de contrôler lespossibilités de communications inter objets,
• L'appel réciproque et récursif de méthodesentre deux (ou plus) objets dans le casasynchrone peut conduire à des situations debouclage, qui ne seraient pas apparues dansle cas synchrone
Classes et Types
La Classe (1)
• On veut créer un nombre arbitraire d’objets• La partie "structure" de l'objet, contenant
ses données, doit être dupliquée pourchaque objet de même type.
• Par contre, les méthodes de ces objets nenécessitent pas d'être décrites plusieurs fois.
La Classe (2)
• On distingue donc entre la réalisationparticulière d'un objet, une instance,
• et l'ensemble des informations nécessairespour construire et "animer" ces instances :leur classe.
Prototypes
• Le modèle utilisé pour générer pour chaqueobjet une structure physique de données estappelé le prototype.
• On peut donc parler de “classe” sans qu'ilne soit question d'héritage.
Types abstraits
• Le concept de type abstrait est introduit pour laspécification de systèmes.
• Un type abstrait consiste en une descriptionformelle (i.e. logique) des états accessibles auxobjets de ce type, et des transitions qui peuventsurvenir entre états.
Types Abstraits et Preuve
• Toutes les propriétés des instances d’un typeabstrait sont démontrables comme on démontre unthéorème.
• Une telle description spécifie donc sans ambiguïtéla sémantique d’un ensemble d’objets,indépendamment des perspectives de sa réalisationinformatique.
des Types abstraits aux langages
• Le type abstrait est un modèle formel des objetsqu'il décrit, et peut être appelé une classe.
• Cette classe comporte assez d'informations pourgénérer un prototype. Les transitionscorrespondent à des fonctions membres quimodifient les instances.
• Mais elle ne possède pas de réalisation physiqueen soi. Elle n'est qu'un cadre général deréalisation.
• C'est l'approche des langages comme C++ etSimula
Type = Classe + Invariant ?
• Une classe décrit ses transitions de manièreimplicite : les fonctions membres quiprovoquent des changements d’état.
• Les états admissibles peuvent être spécifiésde manière semi-formelle par un invariantde classe
• Cet invariant réalise par un test les axiomesapplicables aux objets de cette classe
Types et Virtuelles
• Les langages orientés objet modernespermettent de spécifier des fonctionsvirtuelles.
• Une classe déclarant des méthodesvirtuelles est communément appelée untype.
• En effet, la présence de ces virtuelles obligeà réaliser partiellement le type abstrait
Nécessité de réaliser le type abstrait
• Une classe décrit essentiellement un prototype desobjets de cette classe, et les méthodes applicablesaux objets de cette classe.
• Pour des raisons techniques l'implantation desfonctions virtuelles par les langages orientés objetnécessite que certaines classes engendrent unestructure de données permettant de stocker despointeurs vers des méthodes de la classe.
Table de virtuelles
• La table de virtuelles "concrétise" la classe
C++/Java
• C++ demande de déclarer les fonctionsvirtuelles
• Toutes les méthodes sont virtuelles en Java.
• En Java, le type est réalisé de telle sorte quechaque objet comporte un pointeur vers unereprésentation « consultable » de sa classe.
Variations autour des classes
• tout objet peut servir de prototype à laconstruction d'un autre objet (Javascript)
• tous les types de données sont des objets (enSmalltalk, même un caractère est un objet, quicommunique par envoi de messages avec sesvoisins)
• l'objet classe peut contenir des informationspartagées par toutes les instances de la classe(cela existe en Java, et partiellement en C++)
• une classe est elle même un objet, instance d'unemétaclasse, …
Objets et Classification
• La pensée occidentale développe un modèle dumonde par lequel les propriétés des objetsdécoulent logiquement de la place qu'ils occupentdans une classification.
• La pertinence de la classification est mesurée parle faible nombre d'exceptions qu'elle engendre.
• Ce modèle s'intéresse avant tout aux propriétés desobjets (appelées propositions en logique) plus qu'àleurs éventuelles relations.
Exemple
• Par exemple : si on sait qu'un "objet" est unmammifère, on sait alors qu'il allaite ses petits etqu'il ne vole pas (en général).
• Une classification des concepts nous indiqueégalement que tous les mammifères, et tous lesoiseaux, sont des animaux.
Vision logique de la classe
• Les règles "A" sont descriptives de la classe• Les règles "B" sont des règles d'héritage
Exemple de hiérarchie
Principes d'utilisation de l'héritage
B hérite de, ou possède un A
Une difficulté• Lorsque B hérite de A, la structure de données décrite par
A sera présente d'office dans toute structure de type B.
• La relation d'héritage de classes présente donc un caractèreincrémental qui peut être utilisé comme une facilitéd'écriture.
• (Définissant B comme héritant de A, on réalise l'économiede la description de A dans B, au prix peut être dequelques retouches permises par le langage).
Mauvais exemples
« NON PAS ENCORE EUX »• Faire en sorte que la classe "Cercle" hérite de
"Point"– le cercle réutilise le point par héritage, en tant que
centre• Faire en sorte que la classe "Ellipse" hérite de
"Cercle"– ellipse réutilise le diamètre comme un de ses demi-axes
Point et Cercle
• Un point peut être vu comme un cercledégénéré de diamètre nul. La seule relationlogique qui peut unir ces deux classes estPoint => Cercle.– Dans ce cas, toutes les instances de Point, si
elles héritent de Cercle, vont comporter unchamp appelé "diamètre" qui leur est inutile.
• La bonne approche est peut être de ne pasconsidérer d'héritage entre les deux classes.
Principe Général
• S'il est possible de dire que "tout A est unB", alors
• B ne doit pas hériter de A, et
• A peut, le cas échéant, hériter de B.
Principe pour les données membres
• S'il est possible de dire que tout A peutposséder un B, ou
• S'il est vrai que tout A possède un B, alors
• B est une donnée de A
Héritage pour extension
• On ajoute des données membres.
• Cette situation est toujours présente quandon implante une interface ou une classeabstraite
Héritage pour spécialisation
• On restreint les domaines des attributs de laclasses
• On durcit les invariants de classe (intégrité)
Héritage pour surcharge
• On substitue le code d'une fonction par uneautre, en application du polymorphisme
Héritage pour réutilisation pure
• On l’appelle aussi héritage privé
• On réutilise le code de la classe mais passon interface de programmation
Champs, accesseurs et aspects
Masquage de données
• Un programme ne doit pas exposer lesdonnées membres de ses classes.
• Cette information peut varier au cours desévolutions de la classe et ne doit pas êtredivulguée
• Exemple, une classe « Liste » n’expose pasles détails de son implantation (« Elem »)
Champs, accesseurs, modifieurs
• Un champ (ou attribut, propriété, donnéemembre) est PRIVE
• Accesseur : fonction permettant de lire lavaleur d'un champ
• Modifieur : fonction permettant d'altérerl'état de l'objet
Conventions de nommage
• Si une classe possède une propriété Xy
• L'attribut (privé) est nommé "_xy"• L'accesseur (non Booleén) est "getXy()"• L'accesseur (Booléen) est "isXy()"• Le modifieur est nommé "setXy(...) »
• Ces conventions sont celles des Java Beans
Exemple
Aspects
• D'autres langages plus évolués (plus anciens)règlent le problème du masquage de l'informationd'une autre façon:
• Deux fonctions, les aspects de lecture et d'écriture,sont attachées potentiellement à chaque attribut.
• Si elles sont présentes, elles sont appeléesautomatiquement de façon invisible à chaquelecture/écriture
Aspects, lvalue, rvalue
• Ainsi , faire référence à « a » en situation lvalue(écriture) peut appeler l’aspect correspondant defaçon invisible
• Faire référence à « a » en situation rvalue (lecture)peut appeler l’aspect de lecture de façon invisible
Compatibilité ascendante
• Un argument fort pour le masquage:
• Toutes les versions futures d'une classe devrontpouvoir être utilisées pour compiler desprogrammes clients anciens.
• Les structure de données techniques supportant lesalgorithmes doivent donc être cachés
Exemple d'évolution
Exemple de divergence
Principes de Conception par Contrat
Conception par contrat
• Principe fondamental de conception parcontrat (Bertrand Meyer)
• La spécification des invariants de classe etdes pré et post conditions est préalable aucodage proprement dit.
Conception par contrat et Tests
• La conception des interfaces de programmationdoit se faire dans son ensemble avant deprogrammer.
• L’écriture des programmes de tests doit se faireavant l’implantation concrète des méthodes
• Les programmes de tests peuvent être compilés,même s’ils ne s’exécutent pas
Le principe Open/Closed
La vision moderne de ce principe énoncé par Meyer est lasuivante:
L'interface de programmation d'une classe est:• Open = ouverte aux extensions
– ajout de fonctions membres
• Closed = fermée aux modifications– pas de relâchement de l'invariant de classe ni durcissement des
préconditions, respect du principe de substitution de Liskov
Ce principe est subsumé par les considérations précédentes
Principe de substitution de Liskov
• Une instance d'une classe peut être substituée parune instance d'une sous classe sans que:– la compilation ne soit altérée– le programme ne soit altéré dans son comportement.
• Toutes les clauses du contrat satisfaites par lessuperclasses sont satisfaites par les sous classes
Impact sur les invariants de classe
Les invariants de classe sont vérifiés par toutes leurssous classes
class A{int integrity(){...};
};class B : public A {
int integrity(){assert(A::integrity());...
}
Impact sur les préconditions
Les préconditions des fonctions membres nepeuvent pas être durcies par les sous classes
• Sinon, une fonction appelant la fonctionabstraite pourrait provoquer un échec avecune instance d’une future sous classe(inconnue au moment présent)
Ce principe doit être modulé
• Liskov rend très difficile dans certains casd’utiliser des classes concrètes ayant des sousclasses
• Lorsqu’une sous classe réalise un sous ensembleclairement identifié de la classe, et que sonimplantation offre pour services de contrôlerl’appartenance à cet ensemble, on peut contredirele principe de substitution
Exemple de Cercle et Ellipse• On a vu la possibilité de définir conceptuellement Cercle
comme une sous classe de Ellipse. Dans une interfacegraphique se pose la question du re-dimensionnement:
• La fonction resize(float x, float y) demande un traitementparticulier.
• Il ne devrait pas être possible en vertu du principe desubstitution de durcir la précondition dans Cercle pouravoir "x==y"
• Que fait on?
Ellipse et Cercle (2)
• Choix A/ on respecte le principe de substitution,éventuellement en dégradant arbitrairement lafonctionnalité
void Cercle::resize(float x, float y){ assert(integrity());// demi-axes égauxfloat aux = min(x,y);// on pourra dessiner le cercle dans la boiteEllipse::resize(aux,aux);assert(integrity());//_x==_y
}
Ellipse et Cercle (3)
• Choix B/ on ne respecte pas le principe desubstitution.– L’argument est que si un programmeur avait
voulu utiliser le concept de cercle sans disposerde la classe, il l’aurait exclu des casd’utilisation de la fonction « resize » générale.
Ellipse et Cercle (4)
void Cercle::resize(float x, float y){ assert(integrity());// demi-axes égaux assert(x==y);// contredit LiskovEllipse::resize(aux,aux);assert(integrity());//_x==_y
}
Ellipse et Cercle (5)• Choix B/ on ne respecte pas le principe de substitution et
on interdit l’accès à la fonction d’origine
#define NOT_CALLABLE false
void Cercle::resize(float x, float y){ assert(NOT_CALLABLE );// appel impossible
}void Cercle::resize(float x){
assert(integrity()); // demi-axes égaux Ellipse::resize(x,x);assert(integrity());
}
Impact sur les post conditions
Les post conditions ne peuvent pas être assoupliespar les sous classes
• Sinon, les données retournées, ou l'état de l'objet,pourraient ne plus satisfaire les conditions duprogramme appelant après substitution
• Cette situation n’est pas fréquemment rencontrée,car la principale postcondition est l’invariant declasse, qui est nécessairement satisfait
Principe de Couplage Faible
• Les composants logiciels (classes) distinctssont le plus indépendant(e)s possibles.– en termes de connexions– en termes de création mutuelle
• On vise ainsi à permettre une maintenancefacile du code. L’évolution d’une classe n’aque peu d’impact sur ses voisines
Principe de Cohésion Forte
• Les éléments associés au sein d'une classe ou d’unpackage (groupe de classes) sont fortement liés
• L’intérêt de les avoir groupés est justifié• Toutes les données membres sont toujours (ou
presque) utilisées• Si ce n'est pas le cas, il convient de les séparer
dans des classes distinctes (exemple des itérateurs)
Couplage/Cohésion vs. Héritage
• Dans une classe B qui étend A par héritage,les attributs introduits par B sont– toujours utilisés (cohésion forte) mais ils sont– le plus possible indépendants de ceux qui sont
hérités (couplage faible):
• leurs états n’influent pas ou peu sur l’état duA qui est dans B
Principe d’Etat logique/physique
• Tout modifieur d'une classe altère son étatphysique ET son état logique
• Les modifieurs sont de vrais modifieurs:
• leur appel modifie les données membre de l'objetd'une manière qui altère la sémantique attachée àl'objet.
• Exemple : insertion dans la liste
La liste: Etat Logique
Liste
Elem Elem Elem
Liste
Elem Elem Elem
Elem
La liste: Etat Physique
Liste
Elem Elem Elem
Liste
Elem Elem Elem
Que faire sinon?
• Si un modifieur altère l'état physique (lesdonnées) sans agir sur l'état logique, c'est lesigne que la classe doit être divisée.
• Exemple des itérateurs
List -> ListIterator
Class List {Elem* first;Elem* current;…};
Class List { Elem* first; …};
Class Iterator{ Elem*current;
};
Masquage de données
Les détails d'implantation d'une classe doivent êtrecachés aux utilisateurs
• données membres, classes auxiliaires, fonctionsprivées
• On vise ainsi à empêcher que des programmesaccèdent à des éléments non pérennes(compatibilité ascendante) et également risquentde compromettre les données
Conception des interfaces deprogrammation
Le problème
• Pour garantir une bonne compréhension collectivedes sources développés, il est préférable derenvoyer à des notions très communémentpartagées.
• Le développement du logiciel open source amultiplié les besoins de normalisation desinterfaces de programmation
• Ex en Java : notion de Bean
L'objet vu comme une machine.
• Cette métaphore guide la définition desinterfaces de programmation.
• On voit une machine, et on observe son étatsans agir sur elle
• On agit sur une machine, sansnécessairement observer son état
Le capteur informatique
• Une fonction dont la valeur de retour estune information sur l'état de l'objet ne doitsous aucun prétexte modifier l'état del'objet.
• Une telle fonction est appelée un accesseur.
Signature des accesseurs en C++
data accesseur_i (args) const {…}
• Le mot clef "const" garantit que la fonction estsans effet sur l'objet support
• Le cas échéant, garantir qu'elle soit égalementsans effet sur des objets connectés caractéristiquesd'un état demande de la rigueur de programmation
Le bouton de commande
• Lorsqu'on désire modifier l'état d'un objet,on le fait au travers d'une procédure.
• La règle ici est qu'une telle procédure nepeut en aucun cas renvoyer une valeurdécrivant l'état de l'objet après, ou pireencore, avant la modification.
• On appelle une telle fonction un modifieur.
Signature des modifieurs en C++
void modifieur _i (args) {…}
• "void" garantit que la fonction ne renvoiepas d'information sur l'état de l'objet.
Identification des modifieurs
• Une fonction est un modifieur s'il existe dessituations où un accesseur change de valeurde retour après son appel.
Pourquoi être strict
• Exemple de C (et C++)
i++ + i++
• n'est pas équivalent à
2*i++
Un accesseur/modifieur présente lemême défaut
• Supposons qu'une fonction d'accès présente uneffet de bord.
E1 = (p->value() + p->value())
• sera différent de
E2 = (2 * p->value())
Avantages des modifieurs "void"
• Absence des risques évoqués précédemment• Le calcul d'un état coûte potentiellement des
ressources• Les appels de modifieurs sont fréquemment faits
en ignorant la valeur de retour• Le calcul de cette valeur est alors fait inutilement
– (exemple de char* strcpy(char*) en C)• Il doit exister un accesseur retournant l'état• On ne pourra pas retirer le calcul de la valeur de
retour, et les interfaces sont "fermées".
Les modifieurs retournant "this"
• Il est souvent utile toutefois de faire en sorte queles modifieurs retournent leur objet support
• Cela permet d'enchaîner des cascades d'appels, etjustement de faire suivre le modifieur del'accesseur voulu...
Container c;c.modif_f(params).modif_g(params).accesseur_h();
On veut quand même retourner unétat
• Exemple :
Conversions implicites en C++
• En C++, on peut avoir des modifieurs "this"et toutefois donner accès à l'état de l'objet
La variante create/end• Lorsqu’une fonction crée un objet, il est parfois
utile de retourner celui ci, sinon thiscreateSubState("saveOrPrint"). //{ createReq(ex, "bg", Color.RED). createSubRegion("Print"). //{ createRootState("default"). //{ createReqCallback(pr, …). createSubState("print"). //{ createReq(pr, "on", false). createReq(fr, "Visible", true). endSubState("print"). //} endRootState("default"). //} endSubRegion(). //}endSubState("saveOrPrint"). //}
Cas particulier des opérateurs
• Les opérateurs redéfinis en C++ sontsouvent des modifieurs/accesseurs, dans lamesure où ils participent à des expressionscomplexes.
• L'usage fait que leur "danger" d'utilisationest connu (ex. de "++")
Fonctions d'usage
• Les accesseurs "const" et modifieurs "void" ou"this" forment le squelette des interfaces deprogrammation. Doit en s'en contenter? NON.
• Une API peut mettre à disposition une collectionde fonctions d'usage qui combinent si besoin estmodifieurs et accesseurs
• Les opérateurs en font partie.
Interfaces de programmation
Types de classes
Héritage
• Classe abstraite• Classe concrète• Classe "nœud"
Fonctionnalités
• Classe de service– par exemple l'interface "displayable"
• Classes de définition d'interfaces abstraites– l'abstraction Pile par exemple
• Les classes poignée• Les classes "type de base" vérifié
Poignées
Interfaces abstraites
Fin du document
• Des questions?