===== Programmation avancée ===== ==== Programmation par objets ==== Un objet fusionne des données (caractéristiques structurelles ou variables) et des traitements (caractéristiques comportementales ou opérations) dans un tout dont le type est une classe (//class//). C'est la classe qui déclare les données et définit les opérations (on parle alors de méthodes). Pour créer un objet, on crée un exemplaire de sa classe par une opération appelée instanciation (avec l'opérateur //new//). Dans la classe, une méthode du nom de la classe est utilisée à l'instanciation pour initialiser la valeur des variables de l'objet, on l'appelle un constructeur. Le projet {{:departement_info:personnels:pb:pa3:Compteur0.zip|compteur}} illustre cette fusion données-traitements dans une classe Compteur (représentée ci-après en UML) ainsi que l'instanciation et l'utilisation d'un objet dans la classe EssaiCompteur qui illustre la structure d'un programme java avec le point d'entrée du programme défini dans une méthode appelée //main//.\\ {{:departement_info:personnels:pb:pa3:cpt0.png?150x0&nolink}} La fiabilité du programme n'est pas assurée par la fusion données-traitements puisque les données des objets peuvent être manipulées directement par les programmes qui les utilisent. Mais on peut cacher (rendre privé - //private// noté - en UML) certaines caractéristiques structurelles ou comportementales (des sous-programmes utiles pour définir les opérations exposées). Cela empêche l'accès direct aux données cachées des objets ou le déclenchement des méthodes cachées. On parle de masquage (de l'implantation). Les autres caractéristiques sont alors qualifiées de publiques (//public// noté + en UML).\\ Si on permet l'obtention et/ou la modification de la valeur des données cachées par le biais de méthodes publiques dites informateurs (//getters//) et transformateurs (//setters//), on parle d'**encapsulation**. Cela permet de garantir la **validité des données** d'un objet (valeur dans leur intervalle de définition par exemple) parce que ces méthodes peuvent réaliser des traitements de contrôle avec l'accès proprement dit aux données. Ce projet {{:departement_info:personnels:pb:pa3:Compteur1.zip|compteur}} illustre cette encapsulation dans une classe Compteur (représentée ci-après en UML) ainsi que l'instanciation et l'utilisation d'un objet dans la classe EssaiCompteur.\\ {{:departement_info:personnels:pb:pa3:cpt1.png?150x0&nolink}} Le projet {{:departement_info:personnels:pb:pa3:bonjour1.zip|bonjour}} illustre la structure d'un programme java qui traite les arguments de la ligne de commande comme paramètres du point d'entrée du programme ainsi que :\\ - la possibilité de définir plusieurs méthodes de même nom (saluer de la classe Groupe fait appel à saluer de la classe Personne - on parle ici de délégation)\\ - l'héritage (technique de factorisation du code commun à plusieurs classes - ici VIP a des points communs avec Personne qui lui sont transmis par héritage)\\ - la compatibilité des types d'objets (VIP est compatible avec Personne puisqu'un VIP est une sorte de Personne).\\ {{:departement_info:personnels:pb:pa3:bonjour1.png?400x0&nolink}} Les caractéristiques privées d'une classe sont transmises à ses classes dérivées mais elles n'y sont pas utilisables directement (principe d'encapsulation). Pour transmettre une caractéristique cachée par héritage tout en permettant aux classes dérivées d'y accéder, on utilise la visibilité protégé (//protected// notée # en UML). La qualification de //static// de la méthode //main// est expliquée plus loin (méthode de classe). Le tableau de chaînes de caractères en paramètre de la méthode permet de prendre en compte les arguments de la ligne de commande. Essayer : java Bonjour Luc Maître:Yoda ==== Pour du logiciel de qualité ==== La qualité du logiciel est une appréciation globale basée sur de nombreux {{:departement_info:personnels:pb:pa3:qualité.pdf|indicateurs}}. Elle tient compte de facteurs externes, directement observables par l'utilisateur, et de facteurs internes, observables par les développeurs lors des revues de code ou de la maintenance, qu'elle soit corrective ou évolutive. La maintenance du logiciel est facilité par l'existence d'une documentation du code. Celle-ci peut-être intégrée au code sous forme de commentaires informels qui ne donnent pas de vision globale et obligent à lire tout le code. Une documentation séparée (au traitement de texte) est fastidieuse à rédiger et pratiquement jamais maintenue à jour. La solution est une documentation séparée, produite automatiquement par extraction de commentaires spéciaux intégrés au code comme dans le cas de {{:departement_info:personnels:pb:pa3:javadoc.pdf|JavaDoc}} pour les programmes écrits en Java. Une telle documentation prend du temps mais quand le code change, elle peut être mise à jour en même temps et sans trop d'effort puisqu'elle se trouve dans les fichiers modifiés. La fiabilité du logiciel peut être obtenue par deux approches de programmation :\\ - la {{:departement_info:personnels:pb:pa3:progr_defensive.pdf|programmation défensive}}\\ - la {{:departement_info:personnels:pb:pa3:progr par contrat.pdf|programmation par contrat}}\\ Des {{:departement_info:personnels:pb:pa3:assertion.pdf|assertions}} peuvent être ajoutées dans le code pour s'assurer de certaines conditions. L'avantage des assertions est que le logiciel peut être exécuté de 2 façons :\\ - avec vérification des assertions pendant sa mise au point\\ - sans vérification des assertions pour les utilisateurs finaux (l'exécution est performante car les tests ne sont pas effectués mais le logiciel étant au point, ce n'est plus nécessaire)\\ Le concept d'{{:departement_info:personnels:pb:pa3:exceptions.pdf|exception}} peut aussi être mis en œuvre pour rendre le logiciel robuste. {{:departement_info:personnels:pb:pa3:assert1.zip|exemple 1 d'assertion}} {{:departement_info:personnels:pb:pa3:assert2.zip|exemple 2 d'assertion}} {{:departement_info:personnels:pb:pa3:exception.zip|exemple d'exceptions}} {{:departement_info:personnels:pb:pa3:progr par contrat.zip|exemple de programmation par contrat}} La fiabilité du logiciel peut être vérifiée par des {{:departement_info:personnels:pb:pa3:test.pdf|tests}}. En cas d'échec, les tests permettent de localiser les défauts et de les corriger. Les tests peuvent être automatisés, ce qui rend leur exécution plus rapide. C'est particulièrement utile pour s'assurer de la non-régression puisque cela permet de refaire tous les tests (ceux qui échouaient avant correction du code et aussi ceux qui réussissaient). {{:departement_info:personnels:pb:pa3:junit.pdf|JUnit}} est un cadriciel de test automatisé pour les programmes écrits en Java. {{:departement_info:personnels:pb:pa3:junit.zip|exemple de classe de test}} /!\ Tout programme est un assemblage de composants dont il faut tester **séparément** le bon fonctionnement avant de les assembler. On parle de test unitaire. Lorsqu'on teste le fonctionnement d'un ensemble de composants (2 ou plus), on parle de test d'intégration.\\ Certaines classes en utilisent d'autres pour fonctionner. Pour tester ces classes sans celles qu'elles utilisent, on remplace les classes utilisées par des simulacres ou doublures (//[[https://fr.wikipedia.org/wiki/Mock_(programmation_orient%C3%A9e_objet)|mock objects]]//). Il en existe différentes sortes : bouffon (//dummy//), substitut (//fake//), bouchon (//stub//)... {{:departement_info:personnels:pb:pa3:mock.zip|exemple de doublures}} ==== Exercice pour la prochaine séance ==== Définir une classe Date qui permette le succès de tous les tests automatisés de la classe {{:departement_info:personnels:pb:pa3:testdate.zip|TestDate}}. Ce travail est à rendre par courrier électronique sous l'intitulé "TP01 NFP121" à l'adresse "philippe.brutus (à) caensup.fr" pour le 14/02/2025 à 8h00 avec en pièce jointe le fichier "Date.java". Dans le {{:departement_info:personnels:pb:pa3:date.zip|corrigé}} de cet exercice, la classe Date définit des opérations qui s'appliquent à des dates, c'est-à-dire à des instances (ou des représentants) de la classe Date :\\ - jour\\ - mois\\ - année\\ - jourPlus1\\ - moinsPlus1\\ - anneePlus1\\ ...\\ Ce sont des méthodes d'instance. Ces méthodes sont le seul moyen de connaitre ou modifier les valeurs des données d'une date (principe d'**encapsulation**). Cela garantit la validité de chaque donnée mais aussi la **cohérence des données entre elles** (le jour est cohérent avec le mois - pas de 31 avril, pas de 30 février, pas de 29 février en dehors des années bissextiles). La classe Date définit aussi des opérations utilisables sans instance ou représentant de la classe :\\ - bissextile\\ - nombreDeJoursDuMois Ce sont des méthodes de classe car on les appelle sur la classe elle-même. Ces méthodes sont qualifiées de statiques (//static//).\\ {{:departement_info:personnels:pb:pa3:date.png?300x0&nolink}} La méthode //main// est qualifiée de statique pour cette raison : on doit pouvoir l'appeler sans avoir eu à instancier un objet avant (ce qui ne pourrait être fait que par la méthode //main// elle-même !). Les différentes versions d'essai de la méthode bissextile de la classe Date illustrent de quelles manières ont peut utiliser des méthodes de classe (import de la classe ou import statique de la méthode). (L'organisation des classes en paquets est vue ci-après). ==== Organisation des composants du logiciel ==== Les classes d'un programme sont regroupées dans des ensembles appelés paquets (//package//) qui peuvent contenir eux-mêmes des paquets, organisant ainsi les composants du logiciel en une structure hiérarchique. Une classe déclarée publique dans le paquet qui la contient est visible et utilisable en dehors de ce paquet. Pour l'utiliser en dehors du paquet qui la contient, il faut indiquer où la trouver avec l'instruction //import// suivie du chemin d'accès à la classe. Ce chemin est une suite de noms de paquets séparés par le caractère ':', terminée par le nom de la classe. Une classe qui n'est qualifiée ni de publique, ni de privée ou protégée est visible dans son paquet par les autres classes du paquet. Elle a la visibilité paquet (~ en UML). Le projet {{:departement_info:personnels:pb:pa3:bonjour2.zip|bonjour}} met en œuvre une organisation en paquets des classes.\\ {{:departement_info:personnels:pb:pa3:bonjour2.png?250x0&nolink}} ==== Abstraction ==== Dans le programme bonjour, un groupe contient des personnes. Le tableau dans lequel les membres du groupe sont consignés est déclaré comme un tableau de personnes. Pourtant, on peut y mettre des instances de la classe Personne et aussi des instances de la classe VIP. Cette compatibilité entre objets de types différents est due à l'héritage mais si un VIP est compatible avec une Personne, le contraire n'est pas vrai. En effet, VIP est une spécialisation de Personne alors que Personne est plus général que VIP. C'est pourquoi on parle de généralisation-spécialisation à propos d'héritage. Il est donc possible de déclarer un type pour un objet et d'utiliser un objet d'un autre type à condition que le type déclaré soit le plus général. Avec l'héritage, cette généralisation peut s'exprimer dans des classes qu'il n'est pas concevable d'instancier. On parle de classe abstraite. C'est le cas par exemple dans une gestion de stock d'un magasin qui vend des aliments, des vêtements et d'autres choses encore qui ont en commun des données (référence, désignation, prix de vente, quantité en stock...) et des opérations (ajoute, retire...).\\ {{:departement_info:personnels:pb:pa3:stock.png?1000x0&nolink}} On peut créer des instances de Aliment, de Vetement... mais pas de Article. C'est une généralisation, un concept mais qui n'a pas d'exemplaires dans un stock. On dit que c'est une classe abstraite (//abstract//). Pourtant, c'est une généralisation compatible avec d'autres types d'objets qui peut servir dans une déclaration. Remarque : Un concept abstrait n'est pas instanciable mais une classe non instanciable n'est pas toujours la représentation d'un concept abstrait. Dans les projets précédents, la classe qui définissait la méthode //main// était déclarée abstraite, non pas parce qu'elle représente un concept abstrait, mais parce qu'elle n'est pas instanciable. Dans certains cas, une classe spécifie des opérations sans en donner de définition. On qualifie d'abstraites ces opérations. Par exemple, dans le domaine du déménagement, la classe Meuble contient une opération déplacer parce qu'un meuble peut être déplacé mais on ne sait pas dire comment le faire. Cela dépend du meuble. On pourra donner une définition à l'opération déplacer dans des spécialisations de meuble : Chaise, Table, Buffet... La classe Meuble représente un concept abstrait, pas des objets du réel. C'est une classe abstraite. L'abstraction la plus poussée serait une classe dans laquelle toutes les opérations seraient abstraites et qui ne comporterait aucune donnée. Cela correspond à une interface (de programmation) ou API (//Application Programming Interface//). Pour une classe, l'API est une forme simplifiée de contrat de service énumérant la liste des opérations publiques proposées, sans pré ou post-condition, ni invariant (voir programmation par contrat). Le projet {{:departement_info:personnels:pb:pa3:compteur2.zip|compteur}} illustre la notion d'interface (Compteur) et de classe (CompteurSimple) qui propose les services ainsi spécifiés en implantant l'interface.\\ {{:departement_info:personnels:pb:pa3:cpt2.png?130x0&nolink}}\\ Dans le programme principal, le type utilisé pour déclarer l'objet est l'interface. Cette façon de faire correspond à un principe d'abstraction (le principe de substitution de [[https://fr.wikipedia.org/wiki/Barbara_Liskov|Liskov]]), l'un des 5 principes de conception d'architectures logicielles plus compréhensibles, flexibles et maintenables : {{https://fr.wikipedia.org/wiki/SOLID_(informatique)|SOLID}}. ==== Application de l'abstraction et de l'encapsulation ==== Le projet {{:departement_info:personnels:pb:pa3:heure.zip|heure}} contient une interface Heure et une classe HeureV1 qui implante l'interface avec une représentation intuitive mais peu efficace (occupation mémoire, complexité algorithmique). Une autre classe HeureV2 implante la même interface avec une représentation plus efficace. La classe EssaiHeure comprend 4 méthodes ayant une Heure en paramètre et le programme principal déclare une variable objet h dont le type est Heure. On fait donc abstraction du choix d'implantation de l'interface ...\\ - dans les sous-programmes\\ - dans le programme pour la déclaration de l'objet.\\ La seule instruction qui s'appuie sur un choix d'implantation est l'instanciation. Le principe d'abstraction permet donc de passer d'une implantation à une autre sans pratiquement rien changer au programme. On remarque aussi que l'encapsulation permet des changements de représentation sans incidence sur la manière d'utiliser les objets (puisque l'API est la même et qu'elle est imposée comme seul moyen d'accès aux données - c'est le principe d'encapsulation). ==== Collections et parcours de collections ==== Une collection d'objets peut être représentée par une classe. Les opérations réalisables sur une collection étant communément admises (ajout, recherche, suppression ...), on les spécifie dans une interface. Cette interface pouvant être implantée de différentes manières (tableau, structures chaînées, fichier...), on peut définir plusieurs classes qui implantent (mot clé //implements//) l'interface. Un programme utilisant une collection pourra déclarer la variable servant au stockage des objets en utilisant l'interface comme type. Par contre l'interface et la (ou les) classe(s) ainsi définies doivent pouvoir servir pour différents types d'objets. Il ne faudrait pas devoir définir une classe pour collecter des heures (liste d'alarmes) et en redéfinir une autre pour collecter des dates (liste d'événements) ou des personnes (équipe). On utilise alors des types paramétrés. Le projet {{:departement_info:personnels:pb:pa3:lst1.zip|liste}} illustre ces notions avec l'interface Liste et la classe ListeTableau. Lorsqu'un traitement doit être effectué sur chacun des éléments d'une collection, il faut parcourir cette collection, ce qui peut être fait de différentes façons (à l'endroit, à l'envers...). Tous les parcours sont décomposables en 4 opérations : initialisation, test de poursuite de parcours, obtention de l'élément courant, passage à l'élément suivant. Cela peut être défini dans une classe. Le principe consistant à définir un concept (parcours par exemple) sous forme d'objet s'appelle la [[https://fr.wikipedia.org/wiki/R%C3%A9ification#Informatique|réification]]. La classe correspondante comprendra 4 méthodes spécifiées dans une interface Parcours. On appelle **itérateur** une telle classe de parcours. Le projet {{:departement_info:personnels:pb:pa3:lst2.zip|liste}} illustre ces notions avec les interfaces Parcours et Parcourable et les classes ParcoursALEndroit et ParcoursALEnvers, toutes paramétrées. /!\ Ces classes imbriquées dans la classe ListeTableau sont privées. Elles ne sont donc visibles que dans la classe ListeTableau. Pourtant, elles sont utilisables en dehors même du paquet contenant la classe ListeTableau comme le démontre le programme EssaiListeTableau du paquet application. Cela est rendu possible par l'application du principe de substitution de Liskov. ==== Exercice pour la prochaine séance ==== Compléter la classe ListeChainee présente dans l'archive lst2.zip téléchargeable ci-dessus pour réussir tous les tests automatisés de la classe TestListeChainee. Ce travail est à rendre par courrier électronique sous l'intitulé "TP02 NFP121" à l'adresse "philippe.brutus (à) caensup.fr" pour le 21/02/2025 à 8h00 avec en pièce jointe le fichier "ListeChainee.java". {{:departement_info:personnels:pb:pa3:lst2.png?800x0&nolink}} Ce {{:departement_info:personnels:pb:pa3:lst3.zip|projet}} contient une implantation de la classe ListeChainee qui réussit tous les tests de la classe TestListeChainee. Il montre aussi que le parcours par indice d'une liste chaînée est inefficace (l'exécution de EssaiListeChainee affiche des points à chaque passage sur un élément de liste). ==== Parcours de collection avec for-each ==== Java propose 2 variantes de la structure de contrôle for. La première utilise une variable de boucle qui sert à la répétition (compteur de boucle) ou à l'accès aux éléments d'une collection (indice de boucle ou index des éléments).\\ '' for (int compteur = 0; compteur < 3; compteur++) System.out.print('.');\\ System.out.println();\\ \\ String textes[] = {"un", "deux", "trois"};\\ for (int index = 0; index < textes.length; index++) System.out.println(textes[index]);\\ '' La seconde (dite //for-each//) utilise une variable de boucle qui prend successivement la valeur des éléments de la collection.\\ '' String textes[] = {"un", "deux", "trois"};\\ for (String s : textes) System.out.println(s);\\ '' Pour utiliser cette structure de contrôle sur des collections, il faut que celles-ci soient parcourables, c'est-à-dire qu'elles implantent l'interface [[https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html|Iterable]] de Java. Cela exige que la classe des collections définisse une méthode (//iterator//) qui renvoie un objet de parcours implantant l'interface [[https://docs.oracle.com/javase/8/docs/api/java/util/Iterator.html|Iterator]] de Java. Ce {{:departement_info:personnels:pb:pa3:lst4.zip|projet}} contient une implantation de la classe ListeChainee qui respecte ces exigences. La classe de test TestListeChainee définit une méthode de test de parcours avec //for-each// et la classe d'essai EssaiListeChainee met en œuvre un parcours avec //for-each//. Pour ne pas devoir réécrire nos classes de parcours, nous avons utilisé une autre solution : l'adaptateur. ==== Adapter une classe existante pour se conformer à une API ==== Un adaptateur permet d'utiliser des objets d'une classe présentant des méthodes différentes de celles qu'on voudrait avoir tout en fournissant le service voulu. C'est une classe intermédiaire qui se charge de l'adaptation des API. Cela peut être simple (changement de nom ou changement de nom et appel avec paramètres) ou compliqué (changement de nom et appel de plusieurs méthodes de l'objet adapté dans une méthode de l'adaptateur). {{:departement_info:personnels:pb:pa3:adaptateur.zip|exemple d'adaptateur}} ==== Exercice pour la prochaine séance ==== Modifier la classe ListeTableau présente dans l'archive lst4.zip téléchargeable ci-dessus pour permettre le parcours de ses instances avec la structure de contrôle //for-each//. Modifier les classes ListeTableau et ListeChainee ainsi que leurs classes imbriquées de parcours pour que, pendant un parcours, il soit possible de supprimer de la collection l'élément courant en ajoutant la méthode supprime de l'interface Parcours qui sera traduite dans l'adaptateur correspondant en méthode //remove//. Ce travail est à rendre par courrier électronique sous l'intitulé "TP03 NFP121" à l'adresse "philippe.brutus (à) caensup.fr" pour le 10/03/2025 à 8h00 avec en pièce jointe les fichiers source java des classes ListeTableau et ListeChainee. ==== Principe de réification appliqué à un test ==== Dans {{:departement_info:personnels:pb:pa3:lst5.zip|ce projet}}, le programme EssaiParcoursListeChainee affiche tous les éléments d'une liste, puis seulement ceux qui vérifient un critère particulier.\\ Pour éviter de dupliquer du code en modifiant le test effectué sur chaque élément de la liste ([[https://fr.wikipedia.org/wiki/Ne_vous_r%C3%A9p%C3%A9tez_pas|principe DRY]]), on implante le test comme méthode d'une classe de test qui implante une interface très simple (Test.java) concrétisant ce qu'est un test, c'est la réification. C'est élégant et efficace car la méthode d'affichage n'a pas à être réécrite pour fonctionner avec n'importe quel critère de sélection des éléments de la liste. L'inconvénient de cette solution est de devoir définir une nouvelle classe pour chaque nouveau critère de sélection (voir les classes TestChaineDeLongueur4 et TestToujoursVrai). On peut l'éviter au moyen d'une classe anonyme mais le code écrit est un peu compliqué. Le recours à des expressions anonymes ou {{:departement_info:personnels:pb:pa3:lambda.pdf|expressions lambda}}, simplifie considérablement le code nécessaire. ==== Notion de stratégie ==== On parle de stratégies lorsqu'il est possible de réaliser une opération sur un même objet de différentes manières. On peut par exemple afficher tous les éléments d'une collection horizontalement - les uns à la suite des autres, ou verticalement - les uns en dessous des autres. On peut aussi trier une collection en utilisant différentes méthodes de tri ([[http://lwh.free.fr/pages/algo/tri/tri_bulle.html|tri bulle]], [[http://lwh.free.fr/pages/algo/tri/tri_selection.html|tri sélection]], [[http://lwh.free.fr/pages/algo/tri/tri_fusion.html|tri fusion]], [[http://lwh.free.fr/pages/algo/tri/tri_rapide.html|tri rapide]] ...), ou encore chercher dans une liste chaînée un élément désigné par son indice en commençant le parcours par le début de la liste (indice < moitié du nombre d'éléments) ou en commençant le parcours par la fin de la liste (indice >= moitié du nombre d'éléments). Pour cela, on définit les différentes méthodes de traitement sous la forme d'une méthode //execute()// dans différentes classes qui implantent chacune l'interface [[https://fr.wikibooks.org/wiki/Patrons_de_conception/Strat%C3%A9gie|Strategie]], qui spécifie ces classes de traitement. {{:departement_info:personnels:pb:pa3:stratégie.png?200x0&nolink}} L'objet sur lequel on applique le traitement peut être passé comme argument du constructeur de la classe implantant la stratégie. Dans {{:departement_info:personnels:pb:pa3:lst6.zip|ce projet}}, on a défini une interface Triable pour spécifier des collections sur lesquelles on peut appliquer un tri des éléments et une interface ListeTriable qu'implantent les classes ListeTableauTriable et ListeChaineeTriable.\\ /!\ Les tris s'appuient sur des opérations de base : la comparaison de 2 éléments et l'échange de 2 éléments. La première des deux opérations s'applique à des éléments dont la classe définit une méthode de comparaison. C'est pourquoi le type en paramètre de l'interface ListeTriable est contraint par l'implantation de l'interface //[[https://docs.oracle.com/javase/8/docs/api/java/lang/Comparable.html|Comparable]]//. C'est pour cette raison que l'interface Heure a été déclarée //Comparable// et qu'une méthode //compareTo// a été définie dans les classes HeureV1 et HeureV2. ==== Exercice pour la prochaine séance ==== Les classes ListeTableauTriable et ListeChaineeTriable définissent une méthode trie() qui implante la méthode de tri bulle. Définir sous forme de stratégie (classe implantant l'interface Strategie) le tri bulle (déjà implanté mais pas en stratégie) et le tri sélection, dans les classes ListeTableauTriable et ListeChaineeTriable et modifier la méthode trie() pour que la stratégie appliquée soit le tri bulle quand le désordre est petit et soit le tri sélection quand le désordre est grand (en utilisant la méthode estEnPetitDesordre()). Définir sous forme de stratégie dans la classe ListeChainee, la recherche d'un élément par son indice en commençant par le début et la recherche d'un élément par son indice en commençant par la fin et modifier les méthodes element(int index) et retire(int index) pour qu'elles appliquent l'une ou l'autre des stratégies en fonction de la valeur du paramètre index et du nombre d'éléments de la liste. Ce travail est à rendre par courrier électronique sous l'intitulé "TP04 NFP121" à l'adresse "philippe.brutus (à) caensup.fr" pour le 12/03/2025 à 14h00 avec en pièce jointe les fichiers source java des classes concernées. ---- Dans {{:departement_info:personnels:pb:pa3:lst7.zip|ce projet}}, les classes ListeTableauTriable et ListeChaineeTriable contiennent la définition de classes imbriquées TriBulle et TriSelection qui implantent chacune une des deux statégies de tri... et la classe ListeChainee contient la définition de classes imbriquées RechercheDuDebut et RechercheDeLaFin utilisées dans les méthodes retire(int) et element(int). Dans {{:departement_info:personnels:pb:pa3:lst8.zip|ce projet}}, les classes imbriquées RechercheDuDebut et RechercheDeLaFin, définies dans la classe ListeChainee, héritent d'une classe spécifique de statégie (Recherche) pour simplifier le code des méthodes retire(int) et element(int) en respectant le principe de Liskov. ==== Interface utilisateur graphique ==== Les utilisateurs d'applications informatiques, que ce soit sur ordinateur, sur tablette numérique ou sur téléphone mobile à écran tactile, interagissent avec les applications au moyen d'une interface graphique (GUI pour //Graphical User Interface//) et pas avec une interface en ligne de commande (CLI pour //Command Line Interface//). Une application avec interface en ligne de commande sollicite l'utilisateur en lui affichant des questions et en attendant sa réponse. L'exécution du programme est suspendue pendant que l'utilisateur compose sa réponse au clavier. Ce n'est que lorsqu'il valide sa réponse (en frappant la touche Entrée) que l'exécution du programme reprend. La logique d'utilisation est imposée par le programme. Une application avec une interface graphique réagit aux sollicitations de l'utilisateur en les traitant comme des événements. On parle de [[https://fr.wikipedia.org/wiki/Programmation_%C3%A9v%C3%A9nementielle|programmation événementielle]] pour caractériser le style de programmation qui permet de réagir à des sollicitations très différentes (au clavier, à la souris) et qui peuvent survenir (ou non) à un rythme très variable. En java, les bibliothèques AWT (//Abstract Window Toolkit//) et Swing proposent des classes prédéfinies pour construire des programmes avec interface graphique. On trouve par exemple dans la bibliothèque AWT...\\ - les classes d'événements Clavier (//[[https://docs.oracle.com/javase/8/docs/api/java/awt/event/KeyEvent.html|KeyEvent]]//) et Souris (//[[https://docs.oracle.com/javase/8/docs/api/java/awt/event/MouseEvent.html|MouseEvent]]//)\\ - mais aussi la classe des événements sur des fenêtres graphiques (//[[https://docs.oracle.com/javase/8/docs/api/java/awt/event/WindowEvent.html|WindowEvent]]//)\\ et dans la bibliothèque Swing la classe des Fenêtres graphiques (//[[https://docs.oracle.com/javase/8/docs/api/javax/swing/JFrame.html|JFrame]]//). === Programmation événementielle === Une {{:departement_info:personnels:pb:pa3:appliGraphique.pdf|application avec interface graphique}} doit, pour réagir aux sollicitations de l'utilisateur, définir des méthodes de traitement d'événements qui sont spécifiées dans des interfaces dédiées (//[[https://docs.oracle.com/javase/8/docs/api/java/awt/event/KeyListener.html|KeyListener]]// pour les événements clavier et //[[https://docs.oracle.com/javase/8/docs/api/java/awt/event/MouseListener.html|MouseListener]]// et //[[https://docs.oracle.com/javase/8/docs/api/java/awt/event/MouseMotionListener.html|MouseMotionListener]]// pour les événements souris). Elle doit aussi recenser le ou les objets qui vont exécuter ces méthodes (instances de classes qui implantent ces interfaces) auprès de la source d'événements (une fenêtre par exemple). Remarque : La définition de deux interfaces //MouseListener// et //MouseMotionListener// pour gérer les sollicitations à la souris (//MouseEvent//) est justifiée parce que beaucoup d'applications utilisent la souris (clic, double-clic, entrée ou sortie du pointeur dans ou d'une zone de l'écran...) sans utiliser d'autres sollicitations liées au mouvement de la souris (comme le glisser-déposer). C'est une application du principe ''I'' de SOLID (//[[https://fr.wikipedia.org/wiki/Principe_de_s%C3%A9gr%C3%A9gation_des_interfaces|interface segregation]]//). {{:departement_info:personnels:pb:pa3:appliDeDessin1.zip|Ce projet}} définit une application affichant une fenêtre dans laquelle l'utilisateur peut dessiner à la souris tout en changeant l'épaisseur (caractères 1 à 9) et la couleur (caractère R pour rouge, V pour vert, B pour bleu, J pour jaune, N pour noir) de dessin. Cela illustre la programmation événementielle et la possibilité pour l'utilisateur de solliciter l'application en même temps au clavier et à la souris (enchevêtrement des événements). Si l'on devait décliner cette application pour une utilisation à la souris sur ordinateur ou au(x) doigt(s) sur tablette numérique, il faudrait réécrire le code de la fenêtre de dessin. En effet les événements souris seraient remplacés dans l'une des déclinaisons par des événements d'écran tactile. {{:departement_info:personnels:pb:pa3:appliDeDessin2.zip|Cette variante}} évite la réécriture du code de la fenêtre en séparant vue et contrôleur. {{:departement_info:personnels:pb:pa3:appliDeDessin3.zip|Cette version}} ajoute la possibilité de dessiner des étoiles (en plus des traces) en choisissant un "outil de dessin" (T pour trace, E pour étoile). La solution retenue est une forme de stratégie puisqu'il s'agit d'effectuer un traitement différent pour le même événement (//mouseDragged//). === Architecture MVC === Dans toutes les versions précédentes, on peut diminuer les dimensions de la fenêtre pour masquer une partie du dessin. Mais lorsqu'on augmente les dimensions de la fenêtre pour voir la totalité du dessin, il ne réapparaît pas... parce qu'il n'est pas mémorisé, donc pas réaffichable. {{:departement_info:personnels:pb:pa3:appliDeDessin4.zip|Cette version}} ajoute des classes dans un paquet modele pour mémoriser le dessin. Elle met en œuvre un principe de séparation de l'interface graphique et du noyau fonctionnel d'une application : l'architecture {{:departement_info:personnels:pb:pa3:MVC.pdf|Modèle-Vue-Contrôleur}}... mais sans respecter le principe d'indépendance du modèle à la vue. En effet, les méthodes afficheDans(Graphics) des figures prennent en paramètre un élément de la vue. Comme le même modèle doit pouvoir servir dans des applications aux interfaces utilisateur très différentes, le modèle ne doit pas connaître ni utiliser des éléments de la vue. {{:departement_info:personnels:pb:pa3:appliDeDessin5.zip|Cette version}} réalise l'affichage du dessin dans la classe FenetreDeDessin (méthodes afficheDans et paint) mais doit tester le type des figures alors qu'en programmation par objet, on ne devrait pas tester le type des objets (par application du polymorphisme). === Appel à un ami === Le modèle ne peut pas dépendre de la vue (principe de séparation) et la vue doit traiter (ici afficher) correctement le modèle, c'est-à-dire sans tester le type des objets du modèle. Dans ces conditions, où peut-on définir les méthode d'affichage du modèle ? Dans la vue puisque le modèle doit être indépendant de la vue, mais pas dans la classe FenetreDeDessin, puisque cela oblige à tester le type des objets du modèle. Seule possibilité, dans un classe du paquet vue qui n'est pas la classe FenetreDeDessin. Une classe qui connaît la vue et le modèle... un ami commun. De manière très générale, il s'agit ici de réaliser une opération (l'affichage dans notre exemple) sur des objets de types différents (traces et étoiles dans notre exemple) en définissant les méthodes correspondantes en dehors des classes concernées (Dessin, Trace et Etoile dans notre exemple). C'est un peu comme quand on nous demande quelque chose qu'on ne sait pas, que celui qui demande ne peut le faire, ni nous non plus puisqu'on ne sait pas... On fait appel à un ami ([[https://fr.wikipedia.org/wiki/Qui_veut_gagner_des_millions_%3F|joker dans le jeu "Qui veut gagner des millions"]]). Ou encore quand on nous dit de nous soigner parce qu'on se plaint de douleurs alors que nous ne sommes pas médecin. On ne le fait pas nous-même (la méthode n'est pas définie dans la classe de l'objet qui doit réaliser une opération) mais on le fait faire par un tiers (de confiance), le médecin ou le chirurgien. Ce tiers peut réaliser l'opération sur des objets de classes différentes (un vétérinaire peut opérer un chat, un chien, un serpent...). On définit une interface **Operable** qui spécifie une seule opération ''realise(Operation)''. Cette interface doit être implantée par toutes les classes pour lesquelles on souhaite que l'opération les concernant soit définie en dehors d'elle. On définit une interface **Operation** qui spécifie l'opération pour chacune des classes concernée. On définit une classe pour l'opération en question, qui implante l'interface Operation et définit une méthode pour chaque type d'objet concerné. C'est ce qui est fait dans {{:departement_info:personnels:pb:pa3:appliDeDessin6.zip|cette version}} dont on examinera les interfaces Operable et Operation du paquet modele et la classe Affichage du paquet vue qui définit la méthode d'affichage pour les 3 classes concernées (Dessin, Trace, Etoile). Dans les classes Trace et Etoile du paquet vue, on a défini une méthode ''realise(Operation'' qui permet la réalisation de l'opération (Affichage ou autre) très simplement, puisque le traitement est délégué à l'opération (ami ou médecin ou chirurgien) qui va agir sur l'objet (''op.opereSur(this)''). === Et les différentes couleurs et épaisseurs ? === Le modèle est simplifié dans les versions précédentes. En pratique, dans un logiciel de dessin, l'épaisseur et la couleur s'appliquent à la totalité d'une figure (segment, rectangle, ellipse, trace...). Notre exemple, pour illustrer le potentiel de la programmation événementielle, permet de changer d'épaisseur ou de couleur plusieurs fois dans une figure, ce qui complique le modèle comme on peut le voir dans {{:departement_info:personnels:pb:pa3:appliDeDessin7.zip|cette version}}. === Bibliothèques pour des interfaces utilisateur graphiques === Depuis 1995, la bibliothèque AWT permet de programmer des interfaces utilisateur graphiques (IUG) en proposant :\\ - un ensemble de composants d’interface utilisateur\\ - un modèle robuste de gestion d’événements\\ - des outils de gestion de graphiques et d’images comprenant des classes pour les formes, couleurs et polices de caractères\\ - des gestionnaires d’agencement de composants\\ ... Depuis 1998 avec la sortie de java 2, la bibliothèque Swing offre la possibilité de créer des interfaces indépendantes du système d’exploitation et propose plusieurs choix d'apparence pour chacun des composants standards. Elle s'appuie sur AWT et, en pratique, on utilise {{:departement_info:personnels:pb:pa3:awt.pdf|AWT}} pour les objets qui ne s'affichent pas (événements, écouteurs, agenceurs de composants) et {{:departement_info:personnels:pb:pa3:swing.pdf|Swing}} pour les objets qui s'affichent à l'écran (fenêtres, menus et leurs éléments, boutons divers, zones de texte, curseurs...). En 2008, Sun Microsystems à proposé une alternative à AWT+Swing, plus performante et complète (prise en charge des écrans tactiles par exemple), JavaFX. Reprise par Oracle suite au rachat de java, la bibliothèque JavaFX est intégrée au JDK depuis la version 8 en 2014 et jusqu'à la version 11 de 2018, où le projet est dissocié du JDK. C'est alors la communauté OpenJFX qui poursuit son développement. En tant que bibliothèque externe au JDK, elle est un peu moins pratique à mettre en œuvre que Swing. Notre propos étant la programmation avancée et pas le développement d'interfaces graphiques, nos applications graphiques seront développées avec Swing. ---- ==== Pour partager le travail en cours ==== {{:departement_info:personnels:pb:pa3:partage.zip|partage}}