I N T E RFACE HOMME-MACHINE 1 I1 + T1 HEIA-FR J. BAPST 24.02.2015 S01 Classes, classes abstraites, enum et interfaces OBJECTIFS Mettre en œuvre les principes de base de la programmation orientée objet (encapsulation, héritage et polymorphisme). Définir et coder des classes, des sous-classes, des clases abstraites et des interfaces sur la base d'une spécification. Créer des objets et les utiliser. Exercer les nouvelles notions de programmation présentées au début du cours IHM-1 : énumérations, généricité, annotations et expressions lambda. Consulter et exploiter la documentation (API) de la plate-forme Java. Apprendre à connaître et utiliser des classes génériques du type Collection, notamment List<> et ArrayList<>. Découvrir et utiliser quelques classes de la plateforme Java et de JavaFX (Random, File, Point2D, Color). CONSIGNES Avec Eclipse, créer un projet nommé IHM1 qui sera utilisé pour toutes les séries d'exercices et les TP du cours. Sauf indications contraires, les classes, interfaces et autres éléments seront créés dans des packages dont le nom portera l'identification de la série (s01, s02, ). Des sous-packages pourront être créés, par exemple s01.test pour les classes de test, s01.tools pour des classes utilitaires, etc. D'une manière générale, respecter le principe d'encapsulation et soigner le codage (indentation, choix des identificateurs, conventions de codage, décomposition du code, commentaires appropriés, etc.). Respecter strictement les identificateurs proposés et les spécifications figurant dans les énoncés. Pour les méthodes, les paramètres et les valeurs de retour ne sont souvent pas indiqués; c'est à vous de les déterminer en fonction de l'énoncé. Bien lire et comprendre les énoncés avant de coder. Souvent les exercices s'enchaînent et il vaut la peine de lire l'ensemble des questions avant de débuter la conception et le codage. Prenez l'habitude de tester vos classes et vos méthodes au fur et à mesure de leur écriture (les méthodes de test peuvent même être écrites avant d'implémenter les classes à tester). Pensez à définir des scénarios de test qui ne comprennent pas que le cas général mais qui incluent également des cas particuliers. Dans le rapport qui sera rendu, indiquer clairement les tests effectués et les résultats obtenus (valeurs affichées sur la console, copies d'écran, etc.). Même si les exercices comportent plusieurs phases, seule la version finale du code sera rendue. Les commentaires et remarques devront cependant faire référence aux numéros des exercices de l'énoncé. Les exercices comportent parfois des questions explicitement mentionnées ( QUESTION ). Vos réponses, argumentées, doivent figurer dans le rapport rendu. Il faut profiter des séances de TP pour clarifier les éléments de la matière qui ne sont pas clairs (ne pas compter sur le rendu des TP). IHM1_S01.docx J. Bapst 1
EXERCICES 1 Définir et coder la classe abstraite Message ainsi que les classes concrètes Email, Sms et Mms selon le diagramme de classes suivant : Message Email Sms Mms La classe abstraite Message définira l'adresse de l'expéditeur du message (sender) ainsi que la liste des adresses des destinataires (recipients). Les adresses seront simplement enregistrées dans des chaînes de caractères (String). Pour la liste des destinataires on pourra utiliser un tableau ou, mieux, un objet de type List<String> (interface) en utilisant la classe concrète ArrayList<String>. Étudier préalablement l'api de ces classes qui se trouvent dans le package java.util. La classe Message comprendra une méthode abstraite display() dont le but sera d'afficher le contenu du message (défini dans les sous-classes). Des méthodes concrètes getsender() et getrecipients() permettront de connaître l'expéditeur et les destinataires du message. Dans la classe concrète Email le message sera enregistré sous forme d'une chaîne de caractères (String). Dans la classe concrète Sms le message sera enregistré sous forme d'un tableau caractères (avec de l'espace prévu pour 160 caractères). Dans la classe concrète Mms le message sera enregistré sous forme d'une chaîne de caractères et d'une image en couleur. L'image sera simplement définie et enregistrée comme un objet de type Image (javafx.scene.image.image). Il n'est pas indispensable de connaître les détails de cette classe. Remarque : pour pouvoir créer des objets de type Image en dehors d'une application JavaFX, il faut initialiser le contexte graphique en invoquant new JFXPanel(); au début de la méthode main(). Coder ces différentes classes : Message, Email, Sms, Mms. Dans chaque sous-classe, redéfinir la méthode tostring() (héritée de Object) pour qu'elle retourne une chaîne de caractères avec le contenu du message (par exemple "Serai en retard pour l'apéro, exercice IHM plus long que prévu"). L'implémentation de la méthode display() fera appel à tostring(). Pour la redéfinition des méthodes tostring() (héritée de Object) et display() (héritée de Message), utiliser l'annotation @Override qui permet au compilateur de vérifier que vous respectez bien les entêtes des méthodes telles qu'elles sont définies dans les classes parentes. Pensez à utiliser cette annotation chaque fois que vous redéfinissez des méthodes ou que vous implémentez les méthodes d'une interface. 2 Pour simuler un client de messagerie, créer une classe MailBox qui disposera des méthodes permettant d'envoyer des messages et de consulter le serveur de messagerie (simulé) pour voir si de nouveaux messages sont arrivés : send() receive() envoi d'un message réception du prochain message en attente sur le serveur (s'il y en a un) La méthode send() se contentera de simuler l'envoi en affichant le type de message, le nom de l'expéditeur, celui des destinataires et le contenu du message. Exemple : "SMS envoyé par Bob à Léa : Serai en retard pour l'apéro, exercice IHM plus long que prévu". IHM1_S01.docx J. Bapst 2
La méthode receive() simulera la consultation des différents serveurs de messagerie et se chargera, si un message a été reçu, de le récupérer. Elle simulera cette réception en retournant, au hasard, un message de type Email, Sms, Mms ou aucun message. On codera cette méthode de manière à ce que, dans 50% des cas, elle ne retourne aucun message, dans 30% des cas elle retourne un E-Mail et que les SMS et les MMS ne soient retournés que dans 10% des cas chacun. Indication : la classe Random permet de générer des nombres au hasard et peut être utile pour coder l'aspect aléatoire de la méthode receive(). 3 Écrire un programme permettant de tester votre classe MailBox. Imaginer un scénario de test permettant de tester l'ensemble des fonctionnalités. 4 Imaginer une autre forme de message, si possible assez différente des trois autres et coder cette nouvelle sous-classe de Message. Intégrer votre nouvelle classe dans la classe MailBox et adapter votre programme de test. QUESTION Quelles adaptations ont-elles été nécessaires dans la classe MailBox et dans le programme de test? 5 Créer une classe Rectangle qui modélise un rectangle dans un espace à deux dimensions et dont les côtés sont parallèles aux axes du système de coordonnées. Étudier l'api de la classe Point2D (javafx.geometry.point2d) et utiliser des objets de ce type pour implémenter la classe Rectangle. Définir (au moins) les constructeurs suivants : Rectangle(double x0, double y0, double x1, double y1); Rectangle(Point2D downleft, Point2D upright); La classe comprendra les méthodes suivantes (les valeurs de retour et les paramètres éventuels sont à déterminer) : getwidth() retourne la largeur du rectangle (sur l'axe x) getheight() retourne la hauteur du rectangle (sur l'axe y) isin() détermine si un point se trouve à l'intérieur du rectangle move() permet de déplace le rectangle d'une certaine distance (en x et en y) union() retourne le plus petit rectangle englobant deux rectangles (le rectangle représenté par l'objet courant et un autre rectangle passé en paramètre) intersection() Facultatif retourne le rectangle représenté par l'intersection de deux rectangles (zone de superposition) : le rectangle représenté par l'objet courant et un autre rectangle passé en paramètre. L'intersection peut être vide si les deux rectangles n'ont pas de zones communes. tostring() equals() redéfinir la méthode (héritée de Object) pour qu'elle retourne des informations pertinentes concernent l'état du rectangle (par exemple sous la forme "Rectangle [x0, y0], [x1, y1]"). Utiliser l'annotation @Override. redéfinir (@Override) la méthode (héritée de Object) pour qu'elle retourne true si les rectangles sont égaux (position et dimensions identiques). Écrire un programme de test pour valider le fonctionnement de la classe Rectangle (penser à tester aussi les situations particulières). y r1 x IHM1_S01.docx J. Bapst 3
6 Créer une classe Box qui spécialise la classe Rectangle et qui représente un rectangle que l'on peut afficher à l'écran. Ajouter pour cela les attributs suivants : l'épaisseur de la bordure (de type double) le type de ligne de la bordure (de type énuméré LineType) Créer le type énuméré LineType en tant qu'unité de compilation indépendante (hors de la classe) et définir les constantes PLAIN (continu), DASHED (traitillé) et DOTTED (pointillé). Dans la classe Box, définir les constructeurs nécessaires, ajouter les méthodes qui vous semblent utiles et redéfinir les méthodes tostring() et equals() héritées de la classe parente Rectangle. Compléter le programme de test précédemment écrit pour tester quelques objets de type Box. 7 Après avoir étudié l'api de la classe Color (javafx.scene.paint.color), créer une interface Colorable qui définit des méthodes permettant d'associer une couleur à un objet : paint() whatcolor() permet de définit la couleur de l'objet permet de connaître sa couleur (null si pas définie) L'interface Colorable comprendra également une méthode par défaut permettant d'inverser la couleur de l'objet (en associant la couleur complémentaire) : inversecolor() inverse la couleur actuellement définie (sans effet si aucune couleur n'est définie). Facultatif away() Ajouter à l'interface Colorable une méthode statique qui retourne la couleur possédant un contraste maximal avec une couleur donnée (celle qui est la plus éloignée d'elle dans l'espace colorimétrique RGB) : calcule la couleur la plus éloignée de la couleur passée en paramètre. 8 Adapter la classe Box pour qu'elle implémente l'interface Colorable. La couleur sera simplement enregistrée comme attribut de la classe. Compléter la méthode tostring() pour que les informations associées à la couleur du rectangle soient ajoutées à la chaîne de caractères retournée. 9 Adapter la classe Message pour qu'elle implémente aussi l'interface Colorable (un message coloré, n'est-ce pas sympathique?). Adapter les méthodes tostring() des quatre sous-classes de telle sorte que la méthode display() affiche, en complément, les informations associées à la couleur du message. 10 Créer, dans une nouvelle classe ColorTools, une méthode statique nommée paintall() permettant de colorier tous les objets contenus dans un tableau passé en paramètre avec une couleur déterminée (à passer également en paramètre). On aimerait notamment pouvoir colorier des objets de type Box, Email, Sms, Mms enregistrés dans le tableau passé en paramètre. QUESTION De quel type faut-il définir ce tableau? QUESTION Déclarer le tableau de type Object[] est certainement possible mais quelles en sont les implications? IHM1_S01.docx J. Bapst 4
11 Étudier la classe java.io.file et regarder comment on peut créer un objet de ce type connaissant le chemin d'accès à un fichier ou à un répertoire. Rechercher dans cette classe les méthodes permettant : de déterminer s'il s'agit d'un fichier ou d'un répertoire d'extraire le nom du fichier, avec et sans le chemin d'accès de déterminer la taille d'un fichier Expérimenter l'utilisation de cette classe en créant une classe TestFiles comprenant une méthode main() qui effectuera les opérations suivantes : Création de quelques objets de type File correspondant à des fichiers existants, non-existants et à des répertoires accessibles depuis votre machine. Test des méthodes permettant de déterminer si les fichiers existent, s'il s'agit de répertoires, de retrouver leur chemin d'accès complet, de déterminer leur taille (en bytes). 12 La classe File comprend une méthode list() qui permet de lister le contenu d'un répertoire. La méthode retourne un tableau de String contenant le nom des fichiers (ou sous-répertoires). Tester cette méthode en complétant la méthode main() de votre classe TestFiles. afin de lister le contenu d'un de vos répertoires. 13 Une surcharge de la méthode list(filenamefilter filter) prend en paramètre un filtre qui permet de ne lister que les fichiers qui passent le filtre. Le filtre est représenté par un objet qui doit implémenter l'interface fonctionnelle FilenameFilter. L'unique méthode abstraite de cette interface possède la signature suivante : boolean accept(file dir, String name) Si cette méthode retourne true pour le fichier dont le nom est name, il sera accepté et sera ajouté dans la liste retournée par la méthode list(), sinon, il sera ignoré. Pour créer le filtre, on doit donc implémenter une interface fonctionnelle, ce qui peut se faire de différentes manières : Par une classe 'ordinaire' qui implémente FilenameFilter, puis création d'une instance Par une classe anonyme Par une expression lambda Tester le fonctionnement de la méthode list() en ajoutant dans votre classe TestFiles l'affichage de différentes listes filtrées : Liste de tous les fichiers images (fichiers avec l'extension.jpg,.jpeg,.gif ou.png) Le filtre sera créé sous la forme d'une classe anonyme. Même filtre mais créé sous la forme d'une expression lambda. Facultatif Même filtre mais créé sous la forme d'une classe 'ordinaire'. QUESTION A votre avis, quelle est la meilleure variante? Pourquoi (avantages/inconvénients)? IHM1_S01.docx J. Bapst 5