Notes de cours INFO803, M1-IDESSE et M1-MATH-IM Conception et Programmation orientée objet en Python Jacques-Olivier Lachaud LAMA, Université de Savoie version du 9 février 2010 Table des matières 1 Introduction à l’approche objet 1 1.1 Vision fonctionnelle et vision objet . . . . . . . . . . . . . . . . . 1 1.2 La pertinence de l’approche objet pour l’abstraction . . . . . . . 2 1.3 De l’analyse à l’intégration . . . . . . . . . . . . . . . . . . . . . 5 1.4 Langage adaptée à l’analyse/conception : UML . . . . . . . . . . 6 1.5 Langage de programmation objet : Python 6 . . . . . . . . . . . . 2 Généralités sur le paradigme objet 7 2.1 Objets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 2.2 Vues : conceptuelle, spécification, implémentation . . . . . . . . . 8 2.3 Messages . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.4 Classes ; instance . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 2.5 Attributs d’une classe . . . . . . . . . . . . . . . . . . . . . . . . 9 2.6 Méthodes ; Opérations . . . . . . . . . . . . . . . . . . . . . . . . 9 2.7 Relations entre objets et entre classes . . . . . . . . . . . . . . . 10 2.8 Liens entre objets . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.9 Associations entre classes . . . . . . . . . . . . . . . . . . . . . . 11 2.10 Multiplicités d’une association . . . . . . . . . . . . . . . . . . . . 12 2.11 Généralisation, polymorphisme . . . . . . . . . . . . . . . . . . . 13 1 3 Concepts avancés sur les associations 17 3.1 Agrégation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 3.2 Composition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 3.3 Association qualifiée . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.4 Classe association . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3.5 Contraintes sur les associations . . . . . . . . . . . . . . . . . . . 21 4 Introduction au langage Python 22 4.1 Collections en Python . . . . . . . . . . . . . . . . . . . . . . . . 22 4.2 TODO . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 5 Introduction au langage JAVA 29 5.1 Compilation et exécution d’un programme JAVA . . . . . . . . . 30 5.2 Découpage d’un programme JAVA . . . . . . . . . . . . . . . . . 30 5.3 Types primitifs . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 5.4 Autres types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 5.5 Tableau d’éléments du même type primitif . . . . . . . . . . . . . 32 5.6 Regroupements à l’aide d’une Classe . . . . . . . . . . . . . . . . 32 5.7 Objets et variables objet . . . . . . . . . . . . . . . . . . . . . . . 34 5.8 Constructeurs d’une classe . . . . . . . . . . . . . . . . . . . . . . 35 5.9 Associations en JAVA . . . . . . . . . . . . . . . . . . . . . . . . 35 5.10 Interfaces, classes abstraites . . . . . . . . . . . . . . . . . . . . . 37 5.11 Généralisation, spécialisation, héritage . . . . . . . . . . . . . . . 38 5.12 Polymorphisme . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 6 Aperçu de la bibliothèque JAVA 2 38 Introduction Ce document trace les grandes lignes d’un cours de niveau intermédiaire portant sur la conception par objets et la programmation orientée objet. Le langage objet choisi est le Python. Le langage de modélisation objet est UML. Ce cours s’adresse à des étudiants ayant des compétences en programmation impérative, mais débutant en programmation objet. L’objectif est de connaı̂tre les principes généraux de l’approche objet (classes, héritage, associations, polymorphisme) et de savoir les mettre en œuvre. Pour la notation UML, on se référera au site uml.free.fr. Il existe un nombre impressionnant de livres sur la programmation objet en Python. On pourra ainsi regarder le site de (www.python.org) qui contient des tutoriels Python intéressants. La lecture de ce document n’est pas forcément linéaire. Notamment, il peut être utile de lire en parallèle l’introduction au langage Python. 1 Introduction à l’approche objet 1.1 Vision fonctionnelle et vision objet Dans la vision fonctionnelle, on sépare les données des opérations que l’on effectue dessus. Le processus de conception naturel est donc d’identifier les données manipulées, prévoir des structures de données adaptées, puis écrire des fonctions qui vont prendre une partie de ces données en entrées et fabriquer des nouvelles données en sortie. On parle d’approche systémique. Cette vision est bien adaptée à certaines situations. Les bases de données classiques et la méthode MERISE fonctionnent ainsi sur ce principe. Cela peut être aussi adapté à certains problèmes de calcul scientifiques, où les données en entrée et en sortie sont clairement identifiés. En revanche, cette approche devient délicate lorsqu’il s’agit de modéliser et/ou de simuler des processus plus complexes, avec des données d’origine variée, qui ne se présentent pas toujours sous la forme. L’approche systémique est alors handicapée, car elle cherche à anticiper précisément les données qu’elle manipule. On voit aussi que cette vision implique souvent une analyse ascendante de la problématique, où le concepteur/programmmeur se pose d’abord la question de comment il va stocker ses données. Dans la vision objet, on regroupe les données et les opérations qui sont propres à ces données. Pour résoudre un problème donné, on fait collaborer entre eux des objets qui vont communiquer entre eux pour résoudre le problème. On parle de message passing. De fait, on s’intéresse plus aux services/messages que rendent/comprennent chaque objet qu’à comment les données sont représentées au sein de l’objet. On dispose de plus de moyens pour regrouper des objets entre eux, de façon durable ou non. Normalement chaque objet est responsable de ses données, et un objet ex1 terne n’a pas à les toucher directement. On parle d’encapsulation. Cela fournit une sécurité supplémentaire et permet de décomposer un problème complexe en sous-problèmes indépendants. Une fois qu’un objet est bien validé, son fonctionnement est donc garanti dans l’ensemble du système, aucun autre endroit du programme ne pouvant modifier son comportement. L’approche objet a été proposée pour pouvoir développer des programmes à plus grandes échelles, programmes que l’on pouvait ensuite décomposer plus facilement en sous-problèmes. L’approche objet, qui ne considère que les opérations que peut faire un objet, permet aussi de voir de la même manière des objets avec des spécificités distinctes, mais quelques services communs. Une autre caractéristique de l’approche objet est de pouvoir définir et manipuler des abstractions, ce qui facilite notamment la conception des programmes. Le polymorphisme permet ensuite d’utiliser des réalisations concrètes et spécifiques des abstractions. En informatique, le polymorphisme est l’idée d’autoriser le même code à être utilisé avec différents types, ce qui permet des implémentations plus abstraites et générales. Exemples : L’approche objet est particulièrement efficace pour faire des IHM. On voit bien qu’une IHM doit faire coexister des composants bien différents (fenêtres, dessins, boutons, champs de texte ou de saisie, zones de dessins, tableaux) tout en assurant une mise en page cohérente. Mieux, les dernières IHM permettent d’intégrer à la volée des composants nouveaux (a priori inconnus) mais qui savent répondre à quelques messages. Par exemple, Acrobat Reader peut s’ouvrir dans une fenêtre firefox. Les scènes 3D sont des arbres d’objets. On peut modéliser une fonction à l’aide d’un objet. On peut par exemple modéliser un polynôme sur n’importe quel corps très facilement en programmation objet. 1.2 La pertinence de l’approche objet pour l’abstraction Quelques exemples pour montrer le processus de résolution d’un problème dans une approche objet par rapport à une approche impérative. 2 Quelques questions : 1. Comment modéliser une forme dans une approche impérative ? La question est difficile, car trop floue. On ne voit pas quelle structure de données va convenir à toutes les formes possibles. 2. Comment modéliser une forme dans une approche objet ? C’est plus facile, car on se pose simplement la question : que sait faire une forme ? quelles demandes on va lui faire ? En d’autres termes, à quels messages l’objet sait-il répondre ? Par exemple, une forme doit pouvoir donner son aire, son périmètre, son centre de gravité, ses moments, est-ce qu’un point est à l’intérieur de la forme ou sur le bord ou à l’extérieur, etc. 3. Dans l’approche objet, ce n’est qu’après qu’on se pose la question de comment écrire ces fonctionnalités. D’ailleurs, l’approche objet permet de facilement découper le travail en spécialisant les formes. On aura donc un type Forme, et plein de sous-types qui réalisent des formes particulières : le carré, le disque, le rectangle, l’ellipse, le polygone, la courbe radiale, etc. 4. On aura donc des types abstraits (e.g. Forme) et des types concrets (e.g. Carre), et des relations de sous-typage. Tout type Python indique les messages auxquels il sait répondre : ce sont les fonctions ou méthodes associées au type. 5. On peut se poser la même question pour modéliser un animal, une fonction mathématique, une molécule chimique, une variable statistique, etc. 3 Quelques questions : 1. On dispose dans un carré de côté 1, un ensemble de N carrés de côtés h < 1 deux à deux disjoints. Problème : quelle est la probabilité pour qu’un clic à des coordonnées aléatoires soit dans une des formes ? 2. Si maintenant les carrés n’ont pas tous le même côté. Solution algorithmique ? 3. Si maintenant on a des carrés ou des rectangles ? 4. Si maintenant on peut avoir aussi des cercles ou des ellipses ? On se donne une fonction aire spécialisée pour chaque cas et l’approche objet est alors naturelle. class Forme: def Aire(): pass class Carre( Forme ): def __init( self, cote ): self._cote = cote def Aire(): return self._cote * self._cote class Cercle( Forme ): def __init( self, rayon ): self._rayon = rayon def Aire(): return math.pi * self._rayon * self._rayon Quelques questions : 1. Comment modéliser un vaisseau spatial dans une approche impérative ? Difficile. Sera en plus spécifique à chaque vaisseau. 2. Comment modéliser un vaisseau spatial dans une approche objet ? On peut commencer par préciser les objets qui vont interagir : capitaine, navigateur, chef des transmissions, pilote, mécanicien, etc. Puis on peut préciser les messages auxquels ils savent répondre et les objets avec lesquels ils peuvent interagir. Le capitaine connait tout l’équipage et sait communiquer avec eux. Le navigateur sait calculer une trajectoire pour un objectif donné avec l’ordinateur de bord. Le pilote sait diriger le vaisseau vers un objectif. Le mécanicien sait surveiller et réparer le moteur... Représenter tous ces objets ainsi qu’une séquence de communication suivi par une décision du capitaine d’aller vers une planète. 4 Quelques questions : 1. Comment modéliser un point dans le plan dans une approche impérative ? Même question pour un nombre complexe ? Que constatez-vous ? Ce sont les mêmes données. On pourrait choisir le même type. 2. Comment modéliser un point dans le plan dans une approche objet ? Même question pour un vecteur à deux composantes ? Si on raisonne en termes d’opérations (ie de messages), ce ne sont pas les mêmes objets, donc on créera deux classes différentes. 1.3 De l’analyse à l’intégration Dans tout processus d’informatisation, il est important de distinguer plusieurs étapes (qui ne sont pas forcément réalisées les unes après les autres). Nous en mettons en valeur quelques unes : Analyse (Fait par l’analyste). Il doit comprendre le besoin du client et le traduire sous forme de spécifications. Parfois il est aussi responsable du cahier des charges, qui est un document précisant l’attente du client, et qui est le document contractuel. Il doit comprendre le “Pourquoi” ou le “quoi” du projet. On parle souvent d’analyse fonctionnelle, car il identifie les fonctionnalités attendues. Souvent, il est chargé du chiffrage, en collaboration avec le concepteur. Conception (Fait par le concepteur, ou architecte/concepteur dans des gros projets). Son rôle est de préciser comment réaliser une solution informatique au problème posé, à partir des spécifications de l’analyste. Il doit par exemple proposer un ensemble de systèmes, programmes, soussystèmes, sous-programmes et leur organisation, de manière à montrer un enchaı̂nement global qui répondra au problème posé. Il doit aussi penser à la maintenance future de son programme, et l’anticiper dans sa conception. Chargé du “Comment”. Développement/programmation (Fait par le développeur/programmeur) Il traduit les directives de conception dans un langage/système donné, avec ses spécificités propres. Il doit veiller à respecter à la fois la structure mais aussi les comportements et sémantiques des spécifications. Il sait aussi réutiliser des codes existants. Chargé de la “Fabrication”. Test (Fait par le testeur) Il est chargé de valider le programme développé, notamment en vérifiant que les spécifications initiales du cahier des charges sont respectées. Déploiement/intégration (Fait par l’intégrateur) Il installe l’application développée dans son contexte client et vérifie son intégration. Eventuellement, il paramètre le logiciel en fonction de l’environnement. Remarque : la plupart des études sur le cycle de vie d’un logiciel montre que plus de 70% des coûts proviennent des deux dernières phases. Il est donc important de bien les anticiper dans les phases préparatoires. 5 1.4 Langage adaptée à l’analyse/conception : UML [Wikipédia] UML (en anglais Unified Modeling Language, « langage de modélisation unifié ») est un langage graphique de modélisation des données et des traitements. C’est une formalisation très aboutie et non-propriétaire de la modélisation objet utilisée en génie logiciel. L’OMG travaille actuellement sur la version UML 2.1. Il est l’accomplissement de la fusion des précédents langages de modélisation objet Booch, OMT, OOSE. Principalement issu des travaux de Grady Booch, James Rumbaugh et Ivar Jacobson. UML est un standard défini par l’OMG (Object Management Group). Il ne s’agit que d’une notation, mais UML fournit un langage commun assez clair pour modéliser efficacement l’analyse et la conception objet. 1.5 Langage de programmation objet : Python [Wikipédia] Python est un langage de programmation interprété multi-paradigme. Il favorise la programmation impérative structurée, et orientée objet. Il est doté d’un typage dynamique fort, d’une gestion automatique de la mémoire par ramasse-miettes et d’un système de gestion d’exceptions ; il est ainsi similaire à Perl, Ruby, Scheme, Smalltalk et Tcl. Python est un langage d’assez haut niveau, contrairement à C ou C++. Il fournit en interne des structures de données très puissantes et très faciles à composer. Comme il n’impose que très peu de déclarations, il autorise du développement très rapide. Son côté interprété le rend très pratique comme langage script cross-plateforme. Il est donc très utilisé pour les applications webs. Néanmoins, on constate généralement une perte de performance (de l’ordre de 10 à 20 contre 1 par rapport au C selon des benchmarks). Voilà ci-dessous un premier exemple de code Python (fichier first.py). # Premier code Python print( ’Hello world !’ ) On peut soit choisir d’utiliser un environnement de développement intégré (par exemple idle), soit choisir son éditeur de textes et utiliser les commandes en-ligne : Exécution. On utilise la commande python. # Sous un shell, exécute le fichier first.py python first.py ’Hello world ! 6 2 Généralités sur le paradigme objet 2.1 Objets UML est une notation et définit des éléments de modélisation (notamment graphiques), leurs relations et leurs utilisations pour représenter symboliquement une abstraction d’un problème donné et de la solution de modélisation retenue. Objet : entité/unité ayant une identité et une frontière bien délimitée, possédant des données (les attributs) et offrant des services (les méthodes ou opérations). La notion d’identité est fondamentale : 2 objets sont toujours distincts. Dans l’approche objet, un objet peut représenter une entité réelle (e.g. un livre dans une BD d’un SI bibliothèque, un appartement dans un SI de gestion immobilière, un salarié dans une entreprise, une facture), une entité conceptuelle (e.g. une date, une transaction bancaire, un calcul ou une équation), une entité informatique (une structure de données, un élément d’interface graphique). Exemple : Henri Martin, client de la banque SG a une carte bancaire 1234.5678. Il va retirer de l’argent au GAB SG du cours Gambetta. Celui-ci vérifie avec le SI de la SG que la carte correspond à un compte de la SG. Dessinez les objets participant à ce cas d’utilisation. Henri Martin : ClientCB carte 1234.5678 : GAB SG cours Gambetta : SI SG Comptes Quelques remarques : – l’état d’un objet est la valeur de ses attributs. – Deux objets dans le même état sont appelés des clones. En aucun cas, il ne désigne le même objet. Il y a juste coı̈ncidence de valeurs. – Un objet est toujours une entité concrète (et ne peut être la réalisation d’une classe abstraite, cf. plus loin). – Un objet a une durée de vie. Il nait au moment de sa création ou instanciation et disparait à sa destruction. Une classe est un regroupement d’objets ayant des propriétés communes (mêmes méthodes, mêmes types et dénominations des attributs, mais pas forcément les mêmes valeurs). Exemples : – Les bibliothèques en France fournissent à peu près toutes les mêmes services : fond documentaire, abonnement, consultation, prêt, emprunt, achats. Chaque bibliothèque spécifique (exemple : celle de Chambéry) est un objet. On peut voir le dénominateur commun aux bibliothèques comme une classe, qui offre ces services, mais dont le fond documentaire, les employés et les usagers varient. – Identifiez les types de documents dans une bibliothèque. Cherchez à voir ce qui est commun de ce qui est différent. En déduire des classes. 7 2.2 Vues : conceptuelle, spécification, implémentation Lorsque l’on fait de la modélisation objet, il faut choisir un niveau de représentation qui déterminera souvent ce que sont les objets. Il y a ainsi 3 points de vue sur un système : – le point de vue conceptuel : modélisation du réel, du domaine, du métier. Utile au moment de l’analyse. – le point de vue spécification : niveau logiciel mais seulement interfaces et relations, sans penser au codage réel. Utile au moment de la conception. – le point de vue implémentation : niveau logiciel, représente ce qui est effectivement codé (e.g. les classes utilisées pour implémenter les collections sont données). Utile au niveau codage. Il ne faut pas mélanger les vues, et les faire dans l’ordre (sauf pour du reverseengineering). L’intérêt de l’approche objet est de montrer que ces niveaux de représentation différents fonctionnent de la même façon. Il est par exemple extrêmement fréquent de plus de retrouver les objets identifiés au niveau métier comme des objets persistents du niveau implantation. En général, on a de plus en plus d’objets en descendant dans les niveaux. 2.3 Messages Les objets communiquent entre eux en s’échangeant des messages. C’est le principe du message passing. Un message se caractérise par un nom ainsi que par ses pièces jointes ou paramètres : on parle de signature du message. La liste des messages auxquels peut répondre un objet définit son type. A chaque message que peut comprendre un objet est associé en général une action. Un message se symbolise par une flèche surmontée du nom du message. Le message peut être synchrone ou un simple appel de fonction (il est bloquant), ou asynchrone (non bloquant). Quelques questions : Quel est le type d’une chaı̂ne de caractères ? I.e. que souhaitez-vous pouvoir faire avec une chaı̂ne de caractères. 2.4 Classes ; instance Classe : abstraction d’objets ayant des propriétés similaires mais des attributs pouvant varier. En d’autres termes, une classe permet de représenter dans son ensemble un regroupement d’objets de même type ou de types similaires. En Python, une classe est définie par le mot-clé class. Il n’est nul besoin de prévoir les attributs à stocker. C’est juste en faisant des affectations dans l’objet (self.attr = ...) que l’on crée à la volée des attributs. En revanche, on peut spécifier des méthodes à l’aide de la commande def. Chaque objet pourra ensuite avoir des valeurs distinctes pour ses attributs, mais leurs noms, nombres et types sont fixés. Python suit donc donc une définition très générale 8 d’“objet”, où c’est à la demande (à l’exécution) que l’on vérifiera si l’objet a tel attribut ou telle méthode. Ainsi, on peut par exemple ajouter des méthodes à un objet au cours de sa durée de vie. Une instance de classe est une réalisation concrète d’une classe ; Un objet est donc une instance de sa classe qui le représente. Exemple : Code d’une classe Point. Puis p1 = Point( 5, 0 ) ; p2 = Point( 10, 4 ) ;. p1 et p2 sont des variables qui pointent vers des instances de classe Point. Ils ont les mêmes propriétés (i.e. ont les mêmes méthodes), des attributs de même type mais de valeurs différentes. En Python, pour définir des objets, il faut nécessairement définir une classe qui correspond. 2.5 Attributs d’une classe Les attributs sont caractéristiques de toutes les instances de la classe, même s’ils peuvent prendre des valeurs différentes. Attribut et association sont deux notions proches. – Nommage et typage des attributs – Modifications de visibilité : public (+), protégé (#), privé (-) (défaut : privé) – état d’un objet non Utilisation de notes pour contraindre les attributs non Notion d’attribut dérivé (déjà vu en Merise), noté “/” Exemple : Spécification de Point. Spécification de Complexe. Identiques ? Oui au niveau des attributs. Exemple : Exemple d’attribut dérivé : Personne { age = date actuelle − date de naissance } 2.6 Nom Date de naissance / Age Méthodes ; Opérations Les méthodes ou opérations sont les messages auxquels savent répondre tous les objets de cette classe, en d’autres termes les processus que tout objet de la classe sait mener à bien. Pour raccourcir, on dira souvent les méthodes ou messages que la classe sait mener à bien. – Représentation des opérations : nom, paramètres, typage du retour – Modifications de visibilité : public, protégé, privé (défaut : public) 9 Exemple : Un Point peut être construit à partir de deux coordonnées. Il sait calculer sa distance à un autre point. Point - x : réel - y : réel +Point( x0 : réel, y0 : réel) +distance(p : Point) : réel Spécification des méthodes de Complexe. Identique à Point ? Non au niveau des opérations. On modélise donc les points et les complexes dans des classes différentes. On verra une autre solution avec les liens/associations plus tard. Quelques questions : Modélisez un polygone. Remarque : ce n’est pas parce qu’un attribut ou une opération ne sont pas représentés qu’ils n’existent pas ! Le diagramme n’est qu’une vue, éventuellement partielle, de la classe. Voilà un exemple Python montrant comment instancier des objets dont les classes sont déjà définis dans les bibliothèques d’IHM. from Tkinter import * # Cree une fen^ etre fen1 = Tk() # lui ajoute un texte rouge tex1 = Label( fen1, text=’Bonjour tout le monde !’, fg=’red’) tex1.pack() # et un bouton qui, lorsqu’on clique dessus, détruit la fen^ etre. b = Button( fen1, text=’Export PS’, command = fen1.destroy ) b.pack() # lance la boucle d’attente des événements. fen1.mainloop() 2.7 Relations entre objets et entre classes Les relations indiquent des dépendances statiques entre différentes classes. Ce sont souvent : – association matérialisant un lien entre objets. – relation de généralisation/spécification ou sous-type Dans un premier temps, nous présentons les associations, dont les liens en sont les instances. La relation de généralisation sera abordée ensuite. 2.8 Liens entre objets Certains objets ont souvent des relations particulières avec d’autres objets, relations dont l’existence est supérieure à l’exécution d’une simple opération et persiste dans une certaine durée. On parle de lien entre objets. La notion de lien est très proche de la notion de relation en mathématiques. 10 Un tel lien permet à un des objets reliés d’envoyer des messages à son partenaire et ainsi de lui demander des services. Par ce biais, plusieurs objets vont collaborer à la réalisation d’une tâche. Un lien se dessine comme un trait entre deux objets. Exemple : L’entreprise 1 a un seul fournisseur le fournisseur 1, sans avoir de voiture d’entreprise. L’entreprise 2 a deux fournisseurs (le 1 et le 2) et deux voitures d’entreprise (1 et 2). Fournisseur 1 Entreprise 1 Fournisseur 2 Entreprise 2 Véhicule 1 Véhicule 2 Un tel diagramme est dit diagramme d’objets. On peut nommer les liens ou définir des rôles pour chaque intervenant du lien. Quelques questions : Italine et Pascal sont les parents de Zoë et Clément. Pascal a pour parents Jean et Constance. Les parents d’Italine sont Robert et Martha. Ils ont eu deux autres enfants Max et Gwen. Jean a eu une autre fille Eloı̈se avec Sibylline. Eloı̈se a eu deux enfants Lucien et Raymond avec Honoré. Gwen a un enfant dénommé Bob. Matérialisez les liens parents/enfants. Matérialisez ensuite les liens de cousinage. Est-ce intéressant d’indiquer si père ou mère dans la parentée ? La notion de marriage est-elle intéressante à rajouter ? Pourquoi le cousinage/ancètres n’est pas en général stocké en mémoire ? 2.9 Associations entre classes En général : une association est une connexion bidirectionnelle entre deux classes indiquant un rapport ou une interaction entre elles. Il s’agit d’une vision statique permettant de caractériser les liens possibles entre instances de ces classes. Les associations résument les liens possibles des instances. Le diagramme représentant les classes avec leurs associations/relations s’appelle diagramme de classes. 11 Exemple : Voilà une première solution pour le diagramme de classe des entreprises/fournisseurs. Entreprise Fournisseur Véhicule Expliquer que le rapport classe/objet est le même que le rapport association/lien (voir schémas suivants). Une relation anonyme est rarement significative ; deux méthodes existent pour la préciser : – Il est possible de la nommer en plaçant une forme verbale active ou passive en son milieu. On peut aussi y placer un triangle plein pour indiquer le sens de lecture si nécessaire. – On peut indiquer le rôle de chaque extrémité de l’association du point de vue de la classe située à l’autre extrémité (à préferrer). Ces deux décorations d’association sont exclusives. Contrairement aux liens, les associations peuvent être réflexives. Quelques questions : Corrigez l’exemple précédent d’entreprise/fournisseur en montrant qu’un fournisseur est une entreprise et en donnant des rôles à l’association. Quelques questions : Proposez un diagramme de classes pour la généalogie de l’Exercice 2.8. 2 solutions à ce moment : une classe Personne (avec un attribut sexe et une contrainte) ou 2 classes Homme et Femme. 2.10 Multiplicités d’une association Il est intéressant de placer en regard de chaque extrémité d’une association sa multiplicité, qui précise combien d’instances de cette classe peuvent participer à cette relation. Le diagramme d’objets peut servir de base pour définir les multiplicités dans le diagramme de classes. La cardinalité d’une multiplicité est de la forme : n n..m * n..* Exactement ”n” (entier naturel ¿ 0) De ”n” à ”m” Plusieurs (équivalent à ”0..n” et ”0..*”) ”n” ou plus Insistez sur le fait qu’en UML, multiplicités et rôles sont inversés sur l’association par rapport à MERISE. 12 1..* Entreprise 1..* Fournisseur 1 0..* Véhicule Quelques questions : Mettre les multiplicités sur le diagramme de classes généalogique. Attention au 1 exactement qui impliquerait une BD infinie ! Quelques questions : Modélisez une liste simplement chaı̂née composée d’éléments. Même chose en doublement chaı̂née. Expliquez pourquoi il faut un type pour la liste et un autre pour l’élément encapsulant la donnée. Quelques questions : Modélisez une carte routière (e.g. classes : route, intersection) dont voilà une solution parmi d’autres. 2.11 2.11.1 Généralisation, polymorphisme Typage et classification On rappelle que le type d’un objet est l’ensemble des messages auxquels il sait répondre. Ce type décrit syntaxiquement et sémantiquement les messages auxquels l’objet peut répondre. Pour un langage de programmation classique, c’est l’ensemble de ses méthodes caractérisées par leur signature. En Python, chaque objet est du type défini à son instanciation en l’occurence sa classe. Néanmoins, Python autorise le programme à étoffer son objet de nouveaux attributs en cours de vie de l’objet. En ce sens, Python ressemble aux langages objets Eiffel ou Smalltalk. Le paradigme objet permet de composer les types/classes entre eux/elles pour créer de nouveaux types/classes. Une relation très importante est l’héritage. En Python, on définit un type (abstrait ou concret) sous forme d’une classe avec la syntaxe : class <nom> (object) : On note que, contrairement à d’autres langages, Python ne distingue pas vraiment classes abstraites (ou interfaces) et classes concrètes. En effet, Python utilise le principe du duck typing : si ça cancanne comme un canard et dandine comme un canard, c’est un canard. En d’autres termes, si l’objet possède la méthode au moment de son appel, l’interpréteur exécute la méthode, sinon il y a erreur. Mais on voit bien que le code n’est pas forcément structuré au préalable. C’est juste à l’exécution que tout est vérifié. 13 # Exemple de sous-typage. # Type représentant tout félidé. Seuls les services sont spécifiés. class Félidé( object ): def crier(): raise NotImplementedError # Lion est un sous-type de Félidé, car tous les félidés ne sont pas des lions. # En tant que classe, on met en oeuvre le cri par un rugissement. class Lion (Félidé): def crier(): rugir() ... # Chat est un sous-type de Félidé, car tous les félidés ne sont pas des chats. # En tant que classe, on met en oeuvre le cri par un miaulement. class Chat ( Félidé ): def crier(): miauler() Fig. 1 – Exemple de sous-typage en Python. On voit qu’il faut rajouter une erreur pour rendre la classe Félidé non instanciable (donc abstraite). 2.11.2 Héritage, classes et classification Rappeler la définition ensembliste des types. Une classe est un moyen de réaliser la classification d’entités/objets du modèle. A priori, les objets d’une même classe ont des attributs et propriétés similaires. Exemple : Le zoo a un ensemble d’animaux. Mettre dans une même patate tigres, lions, crocodiles, etc. Ensuite mettre dans une super-patate tigres et lions et la nommer félidés. La généralisation est donc une simple inclusion. On peut faire de la spécialisation en différenciant mâle et femelle pour chaque animal. On peut ensuite faire des patates mâles et femelles. On voit ainsi la notion d’héritage multiple. L’héritage est la faculté d’une sous-classe ou d’un sous-type d’hériter des propriétés de son parent et de les affiner. Dans cette démarche, il existe au moins deux mécanismes d’héritage : – le sous-typage ou l’héritage d’interface (Figure 1), et – la sous-classification ou l’héritage de mise en oeuvre (Figure 2). Le sous-typage est donc le processus par lequel on restreint l’espace des valeurs du type parent, et la sous-classification est le processus par lequel on récupère et on spécialise la mise en oeuvre (redéfinition). Python ne distingue pas vraiment ces deux notions, car il n’a pas vraiment la notion d’interface. Une classe peut hériter de plusieurs classes. On le sépare par des virgules entre les parenthèses. 14 # Exemple de sous-classification. # Classe représentant un rectangle du plan. class Rectangle( object ) def __init__( self, x, y, l, h ): self._x = x self._y = y self._w = w self._h = h def aire(): return self._l * self._h # Classe représentant un carré du plan, obtenu par sous-classification de # Rectangle. class Carre( Rectangle ): def __init__( self, x, y, c ): Rectangle.__init__( self, x, y, c, c ) # redéfinition de aire pour spécialiser sa mise en oeuvre. def aire(): return self._l * self._l Fig. 2 – Exemple de sous-classification, ou héritage de classe/mise en oeuvre en Python. 2.11.3 Polymorphisme, redéfinition Le polymorphisme est l’idée d’autoriser le même code à être utilisé avec différents types, ce qui permet des implémentations plus abstraites et générales. Pour le mettre en œuvre, une classe définie comme sous-classification d’un classe C ou sous-type d’un type T , peut (re)définir la mise en œuvre des méthodes spécifiées dans C ou dans T . Le principe est de réécrire la signature de la méthode et son corps : c’est la redéfinition. Plus précisément, une portion de code attendra de manipuler un objet A d’un type donné T pour lui envoyer le message m(. . .). A l’exécution, n’importe quel objet B dont le type est un sous-type de T pourra se substituer dans cette portion de code. De plus, B conservera la spécificité de sa mise en œuvre de la méthode m(. . .). En fait, à l’exécution, le programme détermine la classe effective de l’objet A pour appeler sa redéfinition de méthode. En Python, c’est très simple. Toute méthode est forcément polymorphe. Un objet d’un type donné possède une liste de méthodes connues. Si on appelle une de ses méthodes elle est appelée. L’exemple ci-dessous utilise les classes définies dans la Figure 2. 15 ... r = Rectangle( 0, 0, 10, 5 ) c = Carre( 0, 0, 10 ) print( r.aire() ) # appelle Rectangle.aire() print( c.aire() ) # appelle Carre.aire() t = [ r, c ] # Aucun problème : Python manipule des collections # arbitraires. aireTotale = 0.0; for f in t aireTotale = aireTotale + f.aire(); # appelle Rectangle::aire() sur t[ O ] # appelle Carre::aire() sur t[ 1 ] : Polymorphisme Le concept de polymorphisme joint à celui d’héritage fait toute la puissance de l’approche objet. Les objets sont manipulables de façon générique, mais peuvent être spécialisés partout où c’est utile. C’est pourquoi on parle souvent de composants en programmation objet. En Python, le duck typing simplifie tout cela, car il ne demande a priori aucune structuration entre classes pour faire du polymorphisme. A l’exécution, on vérifie juste que la méthode existe dans l’objet concerné. 2.11.4 Héritage en UML En UML, on symbolise la relation d’héritage par une flèche terminée par un triangle vide. On parle aussi de relation de généralisation (dans un sens) ou de spécialisation (dans l’autre sens). Etre vivant Végétal Animal Vertébré Arthropode Généralisation simple. Elle exprime que les éléments d’une classe sont aussi décrits par une autre classe : c’est la relation “est une sorte de”. C’est une manière de classifier les classes en une hiérarchie où chaque classe a au plus une superclasse. Généralisation multiple Les classes peuvent avoir plusieurs superclasses. La généralisation multiple consiste à fusionner plusieurs classes en une seule. Cela permet d’exprimer qu’une classe est plusieurs choses en même temps, et rassemble donc plusieurs ensembles de propriétés. On note que l’héritage d’interface se note aussi avec la relation △, avec un tracé de la relation par des tirets. 16 Quelques questions : Les véhicules peuvent être aériens ou terrestres. Un tapis volant est un tapis qui est aussi à l’aise pour transporter des gens dans l’air que sur terre. Dessinez le diagramme de classes. 3 Concepts avancés sur les associations Cette partie présente différents concepts liés aux associations : spécialisation d’association, restrictions, contraintes. 3.1 Agrégation 3.1.1 Définition et notation Une agrégation représente une association non symétrique dans laquelle une des extrémités joue un rôle prédominant par rapport à l’autre. Quelle que soit l’arité, elle ne concerne qu’un seul rôle d’une association. L’agrégation permet par exemple de définir qu’un objet est composé d’un ou plusieurs sous-objets. Elle permet une lecture plus rapide des diagrammes de classe, même si en théorie il suffirait d’avoir des associations. Notons que l’agrégation n’impose a priori pas de réalisation particulière. 3.1.2 Utilisation Pour définir une relation d’agrégation entre des objets, il faut respecter les règles suivantes : – Informellement, l’agrégation respecte les règles suivantes antisymétrie : si un objet A fait partie d’un objet B, alors un objet B ne fait pas partie d’un objet A, même indirectement. Si tel n’est pas le cas, cela veut dire qu’on est en présence d’une simple association. 17 transitivité : si un objet A fait partie d’un objet B, et que B fait partie d’un objet C, alors A doit faire partie d’un objet C dans tous les cas. Sinon, c’est que les champs sémantiques des objets sont mal définis. – Plus formellement, écrire une agrégation entre deux classes (distinctes ou non) impose que toute instanciation de ces classes et associations sous forme de graphe des liens orientés de l’agrégat vers l’agrégé ne contient pas de cycle. L’agrégation peut être utilisée dans les cas suivants : 1. pour les objets qui sont les parties d’un assemblage. Le tout a un comportement global cohérent. Chaque constituant conserve un fonctionnement propre. Exemple : un clavier fait partie d’un ordinateur. 2. pour les objets qui forment les matériaux définissant ensembles un objet. Chaque constituant n’existe plus vraiment en tant que tel mais fait partie d’un tout. Exemple : le pain est fait de farine, d’eau et de levure. 3. pour les objets qui sont des portions d’un objet plus conséquent. Exemple : une coupe dans une image médicale 3D. 4. pour les objets qui sont en relation spatiale ou géographique d’inclusion avec un autre objet. Exemple : Yosemite fait partie de la Californie. 5. pour les objets qui collectés et ordonnés définissent un tout cohérent. Exemple : un voyage est composé de plusieurs trajets. 6. pour les objets contenus dans un conteneur quelconque, sans être forcément du même champ sémantique. Exemple : les employés comme les ressources matérielles font partie d’une entreprise. 3.1.3 Multiplicité Les agrégations peuvent être multiples, même du côté de l’agrégat. Des personnes peuvent être copropriétaires de plusieurs immeubles. 18 Quelques questions : – Reprenez le diagramme de généalogie vu précédemment. Y a-t-il lieu de remplacer une simple association par une agrégation ? Dans les deux cas, expliquez pourquoi ? – Dans une structure d’arbre ou de liste, doit-on employer des agrégations ou des associations ? – Une boı̂te englobante englobe un certain nombre non-nul de points. Une boı̂te englobante définit aussi 2 points particuliers pour représenter son coin supérieur gauche et son coin inférieur droit. Un point peut appartenir à plusieurs boı̂tes englobantes. – Question supplémentaire : on met en relation les boı̂tes englobantes qui sont incluses l’une dans l’autre et les boı̂tes ainsi que les boı̂tes avec une intersection vide. Mettez à jour le diagramme de classe. 3.2 Composition La composition est un cas particulier de l’agrégation, qui implique une inclusion plus forte de l’objet agrégé dans l’objet agrégeant. Alors qu’un même objet peut être agrégé par plusieurs objets différents, un même objet ne peut pas être “composé” (ou partagé) par plusieurs objets. En UML, on utilise la même notation pour la composition que pour l’agrégation, sauf que la pointe ♦ est noircie en ¨. La composition implique une coı̈ncidence des durées de vie des composants et du composite : la destruction du composite implique la destruction de tous ses composants. La création, la modification et la destruction des divers composants sont de la responsabilité du composite. Du côté de l’agrégat, la multiplicité est 0 ou 1 (0 : attribut non renseigné). La composition et les attributs sont sémantiquement équivalents. La notation par composition s’emploie dans les diagrammes de classes lorsqu’un attribut participe à d’autres relations dans le modèle. 19 Exemples : Un B^ atiment se compose d’un certain nombre de Salles. Batiment Salle 1..∗ mon numero : entier ¨ ⇔ Batiment mes salles [1..* ] : Salle La différence entre une composition et un attribut est que la modélisation d’un attribut nécessite la spécification implicite de son type, et non un sous-type, alors que la composition accepte le polymorphisme. Les attributs sont donc un cas particulier de relations de composition : composition par valeur. A noter que la composition permet aussi de représenter facilement les attributs non renseignés (ou non valués). Quelques questions : – Faites le diagrammes de classes des voitures, roues, moteurs. Rajoutez un mécanicien qui peut changer la roue d’une voiture. – Reprenez l’exemple des fonctions. Y a-t-il des agrégations/compositions ? 3.3 Association qualifiée Une association qualifiée est une association qui permet de restreindre les objets référencés dans une association grâce à une clef. Pour naviguer de la classe A vers la classe B via l’association qualifiée du côté de A, l’utilisateur en spécifiant cette clef restreint le nombre d’objets B reliés. Très souvent, seul un objet B est relié à cet objet A avec cette clef. Exemples : – Un train se compose de wagons, identifié par un numéro. Chaque wagon se compose d’un certains nombre de sièges, identifié aussi par leur numéro. – Un plateau de jeu d’échec se compose de 64 cases, identifiable par leurs coordonnées (A-H, 1-8). 3.4 Classe association C’est une classe faisant partie d’une association, et qui permet de stocker des données comme d’associer un comportement à un lien entre deux objets. 20 Exemples : – Créer un diagramme de classe permettant de modéliser la relation de mariage entre deux personnes. Où doit-on stocker la date et le lieu du mariage ? – Dans D & D, un passage entre deux salles peut-il être modélisé comme une classe-association ? On note qu’on peut éclater toute classe-association en deux associations et une classe. 3.5 Contraintes sur les associations La multiplicité est déjà une contrainte. Les autres contraintes se représentent dans des diagrammes par des expressions entre accolades. – La contrainte {ordonné} peut être placée sur le rôle pour spécifier qu’une relation d’ordre décrit les objets de la collection. Le modèle ne spécifie comment les éléments sont ordonnés, mais seulement que l’ordre doit être maintenu durant ajout et suppression des objets par exemple. Exemples : Dans une File d’attente, les Personnes sont ordonnées. – La contrainte {sous-ensemble} indique qu’une collection est incluse dans une autre collection. La contrainte est placée à proximité d’une relation de dépendance entre deux associations, la flèche indique le sens de la contrainte). Exemples : Les délégués sont tous des parents d’élèves. Parent Ecole { Sous−ensemble } Personne Délégué – La contrainte {ou-exclusif} indique que, pour un objet donné, une seule association parmi un groupe d’association est valide. Cela évite l’introduction de sous-classes artificielles pour matérialiser l’exclusivité. Exemples : La sortie d’un amplificateur peut être connectée à un casque ou à une enceinte. Les extrémités d’association peuvent aussi être contraintes : {variable} (défaut), {gelé} (ie constant), {ajoutUniquement} (pour une multiplicité supérieure à 1). Exemples : Un œil a une couleur ({gelé}). Une sculpture est faı̂te d’un matériau ({gelé}). Un historique est composé de faits ({ajoutUniquement}). 21 Quelques questions : Un polygone est composé de points. Faı̂tes le diagramme de classes correspondant. class Point { ... } class Polygone { Point[] m_sommets; // Cree un polygone a nb sommets. Les coordonnees // sont invalides. Polygone( int nb ) { m_sommets = new Point[ nb ]; } // Donne les coordonnees de p au sommet numero i. void setSommet( int i, Point p ) { m_sommets[ i ] = new Point( p.x, p.y ); } 4 Introduction au langage Python Le Python est un langage interprété, ou langage de script. Un programme (dénommé python) lit le code source (un fichier texte) ligne à ligne et exécute les commandes spécifiées. L’avantage est donc qu’un programme écrit en Python est exécutable sur n’importe quelle machine, du moment que vous avez installé python dessus). L’inconvénient est qu’un programme Python est en général plus lent que le même programme écrit dans un langage compilé (comme C, C++, Pascal, Ada). Le Python permet la programmation impérative, objet, et fonctionnelle. Il est donc très versatile. Il est conçu pour rendre plus rapide le développement d’un programme, en minimisant le travail de spécification du programmeur. Par exemple, en C, C++ ou JAVA, il faut prévoir tous les types de données et déclarer leurs attributs et leurs méthodes avant de pouvoir les utiliser. Le Python n’impose rien de tout cela. L’utilisateur peut rajouter quand il le veut un nouvel attribut à un objet. Ce typage est déroutant au début pour qui est habitué à du C++ (très rigide) et JAVA (assez rigide). Il permet néanmoins un apprentissage rapide de la programmation objet et de sa principale qualité : le polymorphisme. 4.1 Collections en Python Python propose plusieurs structures de données pour représenter les collections d’éléments. Chacune est bien adaptée à certaines situations. Conver22 tir l’une en l’autre est facile. Les tuples et les listes sont deux catégories de séquences. 4.1.1 Les uplets ou tuples Il s’agit d’une séquence non modifiable d’éléments. On les crée et manipule ainsi : # tuple à 0 élément t0 = () # tuple à 1 élément t1 = ’hello’, # tuple à plusieurs éléments t3 = 1, 2, 4 t4 = ( 6, ’toto’, 4, ’yup’ ) # tuple de tuples tt = ( (3,2), (5,6), (4,6,7) ) print( len( tt ) ) # donne 3 x, y, z = tt # donne x = (3,2), y = (5,6 ), z = (4,6,7) # On aurait pu écrire x = tt[ 0 ] y = tt[ 1 ] z = tt[ 2 ] Les tuples sont représentés efficacement en Python. On a donc intérêt à les utiliser de préférence à des listes lorsqu’on connait priori le nombre d’éléments de la séquence et que l’on sait qu’il n’y aura pas de modifications ultérieures. On peut comparer l’égalité des tuples, ils ont un ordre lexicographique naturel. On peut itérer sur les tuples comme sur toute collection (commande for). On peut les découper en utilisant la notation []. On peut tester la présence d’un élément x avec la syntaxe if x in tuple : ... 4.1.2 Les listes On les forme avec la notation [ ]. Comme on voit, on peut s’en servir comme les tuples. 23 # liste à 0 élément t0 = [] # liste à 1 élément t1 = [’hello’] # liste à plusieurs éléments t3 = [ 1, 2, 4 ] t4 = [ 6, ’toto’, 4, ’yup’ ] # liste de listes tt = [ [3,2], [5,6], [4,6,7] ] print( len( tt ) ) # donne 3 x, y, z = tt # donne x = [3,2], y = [5,6 ], z = [4,6,7] # On aurait pu écrire x = tt[ 0 ] y = tt[ 1 ] z = tt[ 2 ] On peut comparer l’égalité des listes, ils ont un ordre lexicographique naturel. On peut itérer sur les listes comme sur toute collection (commande for). On peut les découper en utilisant la notation []. Au contraire des tuples, on peut modifier les éléments d’une liste, supprimer des éléments, rajouter ou insérer des éléments. C’est une structure plus versatile que le tuple. 24 l = [ 6, 4, 12 ] l.append( 17 ) l.append( 4 ) # l = [ 6, 4, 12, 17, 4 ] # compte le nombre de fois où 4 est dans l et l’affiche: 2 print( l.count( 4 ) ) # ajoute ces 3 éléments à l l.extend( ( 13, 12, 6 ) ) # Recherche et renvoie l’indice d’un élément (1 ici). print( l.index( 4 ) ) # Retourne la liste l.reverse() # # x y enlève le dernier élément (ou celui d’indice précisé) de la liste et le retourne = l.pop() = l.pop( 2 ) # Trie les éléments l.sort() # Insère un élément avant l’indice donnée. l.insert( 0, 13 ) # Crée un nouvelle liste à partir de l, en prenant tout ou une partie ll = l[:] ll = l[2:4] ll = l[2:] ll = l[:4] On peut aussi utiliser la commande del pour éliminer des éléments d’une liste. 25 >>> >>> >>> [1, >>> >>> [1, >>> >>> [] 4.1.3 a = [-1, 1, 66.25, 333, 333, 1234.5] del a[0] a 66.25, 333, 333, 1234.5] del a[2:4] a 66.25, 1234.5] del a[:] a Ensembles Python propose la classe set pour représenter des ensembles, c’est-à-dire des collections où un élément n’est présent qu’une seule et même fois. Cette classe dispose naturellement des méthodes union, intersection, difference, . . . . s1 = set( (3,4,5) ) s2 = set( (5,6,7) ) s3 = s1.union( s2 ) # s3 = set( 3,4,5,6,7 ) s3.discard( 4 ) # s3 = set( 3,5,6,7 ) s3.add( 8 ) # s3 = set( 3,5,6,7,8 ) if s1.issubset( s3 ): print( ’oui’ ) s3.update( (4, 2, 8, 12 ) ) # s3 = set( 2,3,4,5,6,7,8,12 ) 4 in s3 # True N’oubliez pas qu’une chaı̂ne de caractères est aussi une structure itérable. On peut donc passer en paramètre une chaı̂ne et chaque caractère sera donc un élément de l’ensemble. s1 = set( ’youpi’ ) s2 = set( ’yokaidi’ ) print( s1.union( s2 ) ) # set([’a’, ’d’, ’i’, ’k’, ’o’, ’p’, ’u’, ’y’]) 26 4.1.4 Dictionnaires Les dictionnaires ou tableaux associatifs associent à un ensemble d’éléments (les clés) des valeurs quelconques. En Python, les clés comme les valeurs associées peuvent être de type arbitraire. On définit très simplement un nouveau dictionnaire vide avec les accolades {}. On voit qu’on se sert d’un dictionnaire “comme” d’une liste avec la notation crochet, sauf que l’on peut met une clé en paramètre et non un indice entier. >>> tel = {’jack’: 4098, ’sape’: 4139} >>> tel[’guido’] = 4127 >>> tel {’sape’: 4139, ’guido’: 4127, ’jack’: 4098} >>> tel[’jack’] 4098 >>> del tel[’sape’] >>> tel[’irv’] = 4127 >>> tel {’guido’: 4127, ’irv’: 4127, ’jack’: 4098} >>> tel.keys() [’guido’, ’irv’, ’jack’] >>> ’guido’ in tel True >>> for e in tel.items(): print(e) (’jack’, 4098) (’irv’, 4127) (’guido’, 4127) On peut aussi construire un dictionnaire à partir de listes de couples (clé, valeur) ainsi >>> dict([(’sape’, 4139), (’guido’, 4127), (’jack’, 4098)]) {’sape’: 4139, ’jack’: 4098, ’guido’: 4127} On peut faire beaucoup plus évolué en utilisant le mécanisme des “list comprehensions”. On construit ainsi la fonction carré sur les entiers 2,4,6. >>> dict([(x, x**2) for x in (2, 4, 6)]) {2: 4, 4: 16, 6: 36} 4.1.5 # use a list comprehension Techniques pour parcourir les éléments de collections (repris de www.python.org). Pour visiter tous les éléments d’un dictionnaire, la clé et la valeur correspon27 dante peut être récupérée au même moment avec la méthode iteritems. >>> knights = {’gallahad’: ’the pure’, ’robin’: ’the brave’} >>> for k, v in knights.iteritems(): ... print k, v ... gallahad the pure robin the brave Lorsqu’on boucle sur une séquence, la position et la valeur peut être obtenue en même temps avec la fonction enumerate. >>> for i, v in enumerate([’tic’, ’tac’, ’toe’]): ... print i, v ... 0 tic 1 tac 2 toe Pour boucler sur deux ou plus séquences en même temps, on peut les rassembler avec la fonction zip. >>> questions = [’name’, ’quest’, ’favorite color’] >>> answers = [’lancelot’, ’the holy grail’, ’blue’] >>> for q, a in zip(questions, answers): ... print ’What is your {0}? It is {1}.’.format(q, a) ... What is your name? It is lancelot. What is your quest? It is the holy grail. What is your favorite color? It is blue. Pour boucler sur une séquence en sens inverse, spécifier d’abord la séquence dans le sens avant et appeler dessus la fonction reversed. >>> for i in reversed(xrange(1,10,2)): ... print i ... 9 7 5 3 1 Pour boucler sur une séquence dans l’ordre naturel, on peut utiliser la fonction sorted qui retournera une nouvelle liste dans le bon ordre sans modifier la liste initiale. 28 >>> basket = [’apple’, ’orange’, ’apple’, ’pear’, ’orange’, ’banana’] >>> for f in sorted(set(basket)): ... print f ... apple banana orange pear Quelques questions : Etant donnés 2 séquences s1 et s2, on veut obtenir la séquence des éléments de s1 qui ne sont pas dans s2 et inversement. Quelques questions : Etant donné une chaı̂ne de caractères s, écrire une fonction qui affiche pour chaque lettre combien de fois elle est présente dans s. >>> s = "il fait beau. la mer se prelasse. les enfants barbotent." >>> l = set( s ) >>> print(l) set([’a’, ’ ’, ’b’, ’e’, ’f’, ’i’, ’m’, ’l’, ’o’, ’.’, ’p’, ’s’, ’r’, ’u’, ’t’, ’n’]) >>> d = dict( (a,0) for a in l ) >>> print(d) {’a’: 0, ’ ’: 0, ’b’: 0, ’e’: 0, ’f’: 0, ’i’: 0, ’m’: 0, ’l’: 0, ’o’: 0, ’.’: 0, ’p’: 0, >>> for c in s: ... d[c] = d[c] + 1 ... >>> print(d) {’a’: 6, ’ ’: 9, ’b’: 3, ’e’: 8, ’f’: 2, ’i’: 2, ’m’: 1, ’l’: 4, ’o’: 1, ’.’: 3, ’p’: 1, >>> 4.2 5 TODO Introduction au langage JAVA Le JAVA a pour principale motivation d’être portable (un programme JAVA peut s’exécuter sur n’importe quelle machine/architecture/os du moment que la machine virtuelle JAVA y a été installée). De plus, il s’agit d’un bon langage de programmation objet, qui dispose de presque toutes les possibilités du paradigme objet. Il est de plus assez proche du langage C++, tout en le simplifiant sur certains points, et permet donc une adaptation assez rapide des programmeurs C++. Enfin, il est supposé donner des programmes plus facilement maintenables, car il limite les erreurs dues à la gestion de la mémoire, et offre aussi une bonne gestion des erreurs et exceptions. Son défaut majeur est sans doute sa vitesse d’exécution, entre 3 et 15 fois plus lente qu’un programme C++ similaire (selon les benchmarks). Un autre défaut est qu’il ne permet 29 pas vraiment la programmation système ou la programmation bas-niveau, tout simplement car il introduit une couche d’abstraction entre le programme et l’ordinateur/processeur. En quelque sorte, pour un programme JAVA, toutes les machines sont identiques ! Nous allons présenter ici quelques points de ce langage. 5.1 Compilation et exécution d’un programme JAVA Vous pouvez un IDE comme Eclipse qui se chargera de ces étapes à l’aide de quelques clics. Sinon, JAVA fournit principalement deux programmes : javac compile un ensemble de programmes JAVA en un code que pourra interpréter la machine virtuelle. Si on dispose de deux fichiers JAVA a.java et b.java, on pourra les compiler ainsi > javac a.java b.java Cela génère des fichiers a.class et b.class, qui contiennent des instructions simples exécutables par la machine virtuelle JAVA. java l’interpréteur JAVA ou machine virtuelle JAVA. Elle exécute du bytecode JAVA si on lui donne un point d’entrée : le nom d’une classe publique où il y a un main, par exemple > java a 5.2 Découpage d’un programme JAVA En général, un programme JAVA est découpé en plusieurs fichiers sources d’extension .java. Dans chaque fichier XXX.java se trouve une classe publique de nom XXX. D’autres classes non publiques peuvent en faire partie. Chaque classe contient des données membres (ou attributs) ainsi que des fonctions membres (ou méthodes). Le corps des méthodes se situe juste après leur déclaration et est délimité par un bloc { . . . }. Dans un tel bloc, on peut mettre des instructions. Les fichiers JAVA sont aussi parfois regroupés en package. Si rien n’est précisé (pas de commande package, les classes du fichiers sont dans le package par défaut. 30 // Fichier Exemple.java package Youpi; import java.util.*; // importe quelques classes utiles // Classe publique public class Exemple { // attribut ou donnée membre String s; // méthode ou fonction membre static public void main( String[] args ) { System.out.println( "args[0]=" + args[0] ); } } 5.3 Types primitifs A la base, JAVA ne manipule que les types dits primitifs : int, boolean, char, float, double, etc. Ils permettent de coder des entiers, booléens, nombres à virgule flottante, caractères. Pour déclarer une variable i de type int, on écrit la ligne suivante à l’intérieur d’un bloc { . . . } : { ... int i; // declaration et instanciation i = 5; ... } // fin de la durée de vie de i. La variable i n’existe que jusqu’à l’accolade fermante du groupe où elle a été déclarée. On note qu’on donne à la fois un nom à la variable (ici i) et qu’on lui associe aussi un espace mémoire pour stocker la donnée. 5.4 Autres types On dispose ensuite de plusieurs moyens très puissants pour combiner ces types primitifs en types complexes. – – – – les tableaux [ ] le regroupement des types dans une même classe, sous forme d’attributs l’enrichissement (ou spécialisation) des types via l’héritage la création de types génériques via les patrons ou template On note que seules les variables de type primitif fonctionne comme les variables habituelles des langages impératifs (C, C++, Pascal, Ada). Les variables 31 d’une autre type sont appelées variables objets. On doit bien sûr les déclarer. Ensuite, elles ne font que pointer ou désigner des objets de ce type. Il faudra donc créer (ou allouer, ou encore instancier) des objets du type voulu à l’aide de l’opérateur new. 5.5 Tableau d’éléments du même type primitif Un tableau permet de stocker un nombre donné d’éléments du même type avec un accès direct à chaque élément par le biais de son numéro (ou indice). En JAVA, le type tableau (matérialisé par [ ]) est en fait un objet conteneur. Il faut donc déclarer la variable tableau mais aussi créer l’espace mémoire correspondant avec l’opérateur new. Les indices vont de 0 au nombre d’éléments moins 1, comme en C. Le nombre d’éléments ou taille du tableau est accessible dans le champ length de la variable objet. char[ ] s; // declaration d’une variable tableau de caracteres s = new char[ 10 ]; // instanciation d’un tableau de 10 caracteres s[ 0 ] = ’b’; // la lettre ’b’ est mise dans la case 0 du tableau int i = s.length; // i recoit 10, la longueur du tableau. Une autre façon de faire est d’initialiser le tableau avec des valeurs // déclare et instancie un tableau de taille 6. int[ ] t = { 1, 2, 3, 4, 5, 6 }; 5.6 Regroupements à l’aide d’une Classe On peut s’en servir un peu comme la structure C, mais il s’agit vraiment d’une classe au sens de la programmation objet. On peut donc regrouper des données, mais aussi associer des fonctions membres ou méthodes qui vont opérer sur les données de la classe. On utilise le mot-clé class pour déclarer une classe. Une classe contient essentiellement deux familles d’éléments : – des données, appelées attributs, données membres. Chacune de ces données est associées à chaque objet de ce type. Les données peuvent être de types primitifs ou des classes elles-mêmes. – des opérations, appelées méthodes, fonctions membres. Chacune de ces opérations opèrent sur les attributs de l’objet, en utilisant éventuellement des paramètres et en retournant éventuellement une valeur. Voilà un exemple de classe représentant un point dans le plan. 32 class Point { float x; float y; // Mets les coordonnees (x0, y0) dans le point void init( float x0, float y0 ) { x = x0; y = y0; } // Translate ce point d’un vecteur (u,v) void translate( float u, float v ) { x = x + u; y = y + v; } // Distance à un autre point float distance( Point autre ) { float dx = autre.x - x; float dy = autre.y - y; return Math.sqrt( dx*dx + dy*dy ); } } Et voilà une façon de l’utiliser : // Fichier Exemple1.java public class Exemple1 { public static void main( String[ ] args ) { Point p1; // déclare un point (sans l’instancier !) Point p2; // déclare un autre point (sans l’instancier !) p1 = new Point(); // créer un objet Point p2 = new Point(); // créer un autre objet Point p1.init( 5.0, 4.0 ); p2.init( 10.0, 3.0 ); // Distance de p1 au point p2 => on demande a p1 de la calculer. double d12 = p1.distance( p2 ); // Distance de p2 au point p1 => on demande a p2 de la calculer. double d21 = p2.distance( p1 ); System.out.println( d12 + " = " + d21 ); } } Pour compiler et exécuter ce code, on tapera dans un terminal : > javac Exemple1.java > java Exemple1 5.0990195135927845 = 5.0990195135927845 33 5.7 Objets et variables objet Chaque fois que l’on appelle l’opérateur new on crée quelque part en mémoire un nouvel élément du type spécifié, un objet donc. C’est en fait la seule et unique manière de créer des objets en JAVA. Pour accéder à un objet, on utilise les variables objets, qui vont pointer vers les objets créés, et qui vont nous permettre d’accéder à leurs champs et de leur encoyer des messages. Plusieurs variables objets peuvent donc référencer ou pointer vers le même objet. ... Point p1 = new Point(); Point p2 = new Point(); Point p3 = p1; // p3 reference le meme objet que p1. p1.init( 5.0, 4.0 ); p2.init( 10.0, 3.0 ); p3.x = 0.0; System.out.println( p1.x ); // affiche 0.0 ! On accède aux champs et méthodes d’un objet via le symbole ”.” appliqué sur une variable objet qui le référencie. Une variable objet de type T peut désigner tout objet de type T ou d’un sous-type de T . En JAVA, toute classe est un sous-type de la classe Object. On peut donc toujours écrire : ... Object o = new Point(); ... Cela a un sens, notamment lorsqu’on manipule des collections d’éléments de types différents. On peut toujours revenir au type initial (type instancié) d’un objet avec le transtypage. ... Object o = new Point(); Point p1 = (Point) o; // valide Polygon poly = (Polygon) o; // invalide, Polygon n’est pas // un sous-type de Point ... Une variable objet non initialisée vaut null. 34 5.8 Constructeurs d’une classe Ce sont des méthodes particulières qui ne sont appelées qu’à la création de l’objet (son instanciation). Cela permet de créer un objet qui a tout de suite des données valides. Ces méthodes portent simplement le nom de la classe, peuvent prendre des paramètres et n’ont pas de valeur de retour. Pour la classe Point, on pourrait proposer class Point { ... public Point() { x = 0.0; y = 0.0; } public Point( double x0, double y0 ) { x = x0; y = y0; } public Point( Point autre ) { x = autre.x; y = autre.y; } ... } ce qui permettrait les initialisations suivantes à l’instanciation : ... Point p1 = new Point(); // p1.(x,y) = (0.0, 0.0) Point p2 = new Point(3.0, 2.0); // p2.(x,y) = (3.0, 2.0) Point p3 = new Point(p2); // p3.(x,y) = (3.0, 2.0) ... 5.9 Associations en JAVA Une variable objet permet de pointer vers un objet JAVA. Pour relier un objet JAVA à un autre objet JAVA, il suffit donc qu’une variable objet (du bon type) soit déclarée comme donnée membre de la classe. Par exemple, si tout objet de type A est relié à un objet de type B, la classe A s’écrira : 35 class A { ... B mon_b; ... }; // donnee membre publique, privée, ou autre Inversement, si l’objet de type B doit connaı̂tre cet objet de type A associé, il faudra aussi déclarer une donnée membre dans la classe B. class B { ... A mon_a; ... }; // donnee membre publique, privée, ou autre Comme exemple, voilà une façon d’écrire une classe Segment, qui représente un segment entre deux points. class Segment { Point p; Point q; ... double longueur() { return p.distance( q ); } } Si une classe A est associée à possiblement plusieurs instances d’une classe B ayant le même rôle, on utilisera un tableau de variables objet. Par exemple, on peut définir un Polygone ainsi : class Polygon { Point[] sommets; ... Polygon( int n ) { // Cree un polygone regulier à n sommets sommets = new Point[ n ]; for ( int i = 0; i < n; i++ ) { double a = ( 2.0 * Math.PI * i ) / n; sommets[ i ] = new Point( Math.cos( a ), Math.sin( a ) ); } } } 36 Quelques questions : 1. Ecrivez la classe Personne et ses relations de parenté. 2. Ecrivez les classes Homme et Femme et ses relations de parenté. 3. Y a-t-il une différence entre les attributs de type String (chaı̂ne de caractère) utilisés pour stocker le nom et prénom de la personne et les attributs désignant les objets en relation de parenté. 4. Reprenez l’exemple de la classe Fonction et proposez une classe modélisant l’addition de deux fonctions. 5.10 Interfaces, classes abstraites On ne s’intéresse parfois qu’aux opérations associées à un concept, et aucunement aux attributs ou au code nécessaire pour le mettre en oeuvre dans un cas précis. JAVA offre un mécanisme pour modéliser ces abstractions, les interfaces. Par exemple, voilà une interface possible pour les formes du plan : interface Forme { double aire(); double perimetre(); boolean pointInterieur( Point p ); Point centreDeGravite(); Rectangle boiteEnglobante(); } Beaucoup d’applications et de fonctions n’ont pas vraiment besoin de savoir comment sont écrites ces méthodes, mais ont juste besoin de les appeler. Ainsi, elles peuvent prévoir simplement de recevoir une Forme en paramètre, et non une spécialisation concrète. On pourra ensuite préciser qu’une classe réalise concrètement ces opérations avec le mot-clé implements. class Carre implements Forme { int m_c; double aire() { return m_c*m_c; } ... } Une classe abstraite est spécifiée par le mot-clé abstract. Il s’agit d’un intermédiaire entre une interface et une classe, au sens ou certaines méthodes peuvent ne pas être écrites, mais juste spécifiées. Une classe abstraite peut ou non avoir des attributs, contrairement aux interfaces qui n’ont aucun attribut. On note qu’aucun objet ne peut être du type d’une interface ou d’une classe abstraite, car les objets sont concrets. En revanche les objets peuvent bien sûr 37 être des mises en oeuvre concrètes d’une ou plusieurs interfaces ou d’une classe abstraite : le type de l’objet à l’instanciation est alors un sous-type de ces types abstraits. Ainsi on instanciera des Carres, des Ellipses, mais jamais des Formes. 5.11 Généralisation, spécialisation, héritage (Voir la Section 2.11.) Le JAVA dispose de deux mécanismes de spécialisation : – le sous-typage ou héritage d’interface : il permet de définir des spécialisations de types existants. Dans l’approche ensembliste, il s’agit de découper un ensemble d’éléments en sous-ensembles consistants, qui offrent des opérations supplémentaires. Il ne s’agit pas de préciser comment ces opérations sont effectivement réalisées et quelles sont les données associées. Le sous-typage se fait en utilisant le mot-clé implements. Une interface ou une classe peut hériter d’autant d’interfaces qu’elle le souhaite. – la sous-classification ou héritage de mise en œuvre : il permet d’acquérir toutes les propriétés d’une autre classe (méthodes/opérations, données, implémentations des méthodes), de rajouter de nouvelles méthodes et/ou données, voire de redéfinir certaines méthodes pour les spécialiser. La sousclassification se fait en utilisant le mot-clé extends. En JAVA, seule une classe peut hériter d’une seule autre classe. Par défaut, une classe hérite de la classe Object. Cela induit que toute classe dérive de la classe Object en JAVA. On note donc que toute classe est un sous-type de Object. Le sous-typage (et donc le polymorphisme) peut se faire via le mécanisme d’héritage d’interface ou d’implémentation. On réfère le lecteur aux deux exemples de la Section 2.11. 5.12 Polymorphisme Quelques questions : 1. Reprenez l’exemple des formes. Ecrire l’interface Forme ainsi que les différentes classes concrètes pour les formes : polygone, carré, cercle, ellipse, fonctions implicites, etc. 2. Montrez un exemple d’utilisation où on calcule l’aire totale de beaucoup de formes de type concret différent. 6 Aperçu de la bibliothèque JAVA On parle aussi de l’API JAVA (pour Application Programming Interface). C’est une bibliothèque de classes déjà écrite, utilisable facilement par tout programme(ur) JAVA. Notamment, on dispose de classes pour : 38 – faire des calculs mathématiques – gérer les fichiers – manipuler des collections d’éléments – faire des IHM (interfaces homme-machine avec fenêtres, boutons, et autres) – communiquer avec une base de données – gérer les images, vidéos, sons – établir des connexions réseaux, communiquer sur Internet – faire de la programmation concurrente (multithreading) – etc Il ne s’agit pas ici de présenter cette bibliothèque de manière exhaustive. Nous référons le lecteur au site de Sun, qui met en-ligne toute l’API ainsi que des tutoriaux pour rapidement savoir l’utiliser. On va juste survoler quelques points. 39