37 0 726KB
Architecture logicielle
Les patrons de conception
1
Plan Introduction Les patrons d’interface
2
Qu’estQu’est-ce qu’un pattern ? Un pattern, ou modèle, est un moyen d’accomplir quelque chose, un moyen d’atteindre un objectif, une technique. Le principe est de compiler les méthodes éprouvées qui s’appliquent à de nombreux types d’efforts, tels que la fabrication d’aliments, d’artifices, de logiciels, ou autres. La communauté informatique a fait sienne cette approche en créant de nombreux ouvrages qui documentent des modèles de développement logiciel. Ces livres consignent les meilleures pratiques en matière de processus logiciels, d’analyse logicielle, d’architecture de haut niveau, et de conception de niveau classe. 3
Qu’estQu’est-ce qu’un pattern de conception ? Un pattern de conception (design pattern) est un modèle qui utilise des classes et leurs méthodes dans un langage orienté objet. Les développeurs commencent souvent à s’intéresser à la conception seulement lorsqu’ils maîtrisent un langage de programmation et écrivent du code depuis longtemps. Les patterns de conception interviennent un niveau au-dessus du code et indiquent typiquement comment atteindre un but en n’utilisant que quelques classes. Un pattern représente une idée, et non une implémentation particulière. 4
Pourquoi ? D’autres développeurs ont découvert avant vous comment programmer efficacement dans les langages orientés objet. Si vous souhaitez devenir un programmeur Java avancé, vous devriez étudier les patterns de conception. On dénombre au moins cent patterns qui valent la peine d’être connus. On se limitera à l’étude de 23 patrons
5
Patterns d’interface ADAPTER : fournit l’interface qu’un client attend en utilisant les services d’une classe dont l’interface est différente. FACADE : fournit une interface simplifiant l’emploi d’un sous-système. COMPOSITE : permet aux clients de traiter de façon uniforme des objets individuels et des compositions d’objets. BRIDGE : découple une classe qui s’appuie sur des opérations abstraites de l’implémentation de ces opérations, permettant ainsi à la classe et à son implémentation de varier indépendamment. 6
Patterns de responsabilité SINGLETON : garantit qu’une classe ne possède qu’une seule instance, et fournit un point d’accès global à celle-ci. OBSERVER : définit une dépendance du type un-à-plusieurs (1,n) entre des objets de manière à ce que lorsqu’un objet change d’état, tous les objets dépendants en soient notifiés et soient actualisés afin de pouvoir réagir conformément. MEDIATOR : définit un objet qui encapsule la façon dont un ensemble d’objets interagissent. Cela promeut un couplage lâche, évitant aux objets d’avoir à se référer explicitement les uns aux autres, et permet de varier leur interaction indépendamment. PROXY : contrôle l’accès à un objet en fournissant un intermédiaire pour cet objet. CHAIN OF RESPONSABILITY : évite de coupler l’émetteur d’une requête à son récepteur en permettant à plus d’un objet d’y répondre. FLYWEIGHT : utilise le partage pour supporter efficacement un grand nombre d’objets à forte granularité. 7
Patterns de construction BUILDER : déplace la logique de construction d’un objet endehors de la classe à instancier, typiquement pour permettre une construction partielle ou pour simplifier l’objet. FACTORY METHOD : laisse un autre développeur définir l’interface permettant de créer un objet, tout en gardant un contrôle sur le choix de la classe à instancier. ABSTRACT FACTORY : permet la création de familles d’objets ayant un lien ou interdépendants. PROTOTYPE : fournit de nouveaux objets par la copie d’un exemple. MEMENTO : permet le stockage et la restauration de l’état d’un objet. 8
Patterns d’opération TEMPLATE METHOD : implémente un algorithme dans une méthode, laissant à d’autres classes le soin de définir certaines étapes de l’algorithme. STATE : distribue la logique dépendant de l’état d’un objet à travers plusieurs classes qui représentent chacune un état différent. STRATEGY : encapsule des approches, ou stratégies, alternatives dans des classes distinctes qui implémentent chacune une opération commune. COMMAND : encapsule une requête en tant qu’objet, de manière à pouvoir paramétrer des clients au moyen de divers types de requêtes (de file d’attente, de temps ou de journalisation) et de permettre à un client de préparer un contexte spécial dans lequel émettre la requête. INTERPRETER : permet de composer des objets exécutables d’après un ensemble de règles de composition que vous définissez.
9
Patterns d’extension DECORATOR : permet de composer dynamiquement le comportement d’un objet. ITERATOR : fournit un moyen d’accéder de façon séquentielle aux éléments d’une collection. VISITOR : permet de définir une nouvelle opération pour une hiérarchie sans changer ses classes.
10
Patterns d’interface Introduction aux interfaces ADAPTER FACADE COMPOSITE
11
Introduction aux interfaces L’interface d’une classe est l’ensemble des méthodes et champs de la classe auxquels des objets d’autres classes sont autorisés à accéder. L’implémentation d’une classe est le code contenu dans ses méthodes. Les interfaces Java permettent à plusieurs classes d’offrir la même fonctionnalité et à une même classe d’implémenter plusieurs interfaces.
12
Interfaces et classes abstraites Si les interfaces n’existaient pas, vous pourriez utiliser à la place des classes abstraites, comme dans C++. Les interfaces jouent toutefois un rôle essentiel dans le développement d’applications multiniveaux, ce qui justifie certainement leur statut particulier de structure distincte.
13
Exemple Considérez la définition d’une interface que les classes de simulation de fusée doivent implémenter. Les ingénieurs conçoivent toutes sortes de fusées, qu’elles soient à combustible solide ou liquide, avec des caractéristiques balistiques très diverses. Indépendamment de sa composition, la simulation d’une fusée doit fournir des chiffres pour la poussée (thrust) et la masse (mass). Voici le code qu’utilise Oozinoz pour définir l’interface de simulation de fusée : package com.oozinoz.simulation; public interface RocketSim { abstract double getMass(); public double getThrust(); void setSimTime(double t);} 14
Exercice 1 Parmi les affirmations suivantes, lesquelles sont vraies ? Les méthodes de l’interface RocketSim sont toutes trois abstraites, même si seulement getMass() déclare cela explicitement. Les trois méthodes de l’interface sont publiques, même si seulement getThrust() déclare cela explicitement. L’interface est déclarée public interface, mais elle serait publique même si le mot clé public était omis. Il est possible de créer une autre interface, par exemple RocketSimSolid, qui étende RocketSim. Toute interface doit comporter au moins une méthode. Une interface peut déclarer des champs d’instance qu’une classe d’implémentation doit également déclarer. Bien qu’il ne soit pas possible d’instancier une interface, une interface peut déclarer des méthodes constructeurs dont la signature sera donnée par une classe d’implémentation. 15
Interfaces et obligations Un avantage important des interfaces Java est qu’elles limitent l’interaction entre les objets. Une classe qui implémente une interface peut subir des changements considérables dans sa façon de remplir le contrat défini par l’interface sans que cela affecte aucunement ses clients. Un développeur qui crée une classe implémentant RocketSim a pour tâche d’écrire les méthodes getMass() et getThrust() qui retournent les mesures de performance d’une fusée. Autrement dit, il doit remplir le contrat de ces méthodes. Parfois, les méthodes désignées par une interface n’ont aucune obligation de fournir un service à l’appelant. Dans certains cas, la classe d’implémentation peut même ignorer l’appel, implémentant une méthode avec un corps vide. 16
AuAu-delà des interfaces ordinaires
17
Oozinoz Les exercices et exemples de cette partie citent tous des exemples d’Oozinoz Fireworks, une entreprise fictive qui fabrique et vend des pièces pour feux d’artifice et organise des événements pyrotechniques. Vous pouvez vous procurer le code de ces exemples à l’adresse www.oozinoz.com.
18
ADAPTER Un objet est un client lorsqu’il a besoin d’appeler votre code. Si une classe existante est en mesure d’assurer les services requis par un client mais que ses noms de méthodes diffèrent, vous pouvez appliquer le pattern ADAPTER. L’objectif du pattern ADAPTER est de fournir l’interface qu’un client attend en utilisant les services d’une classe dont l’interface est différente.
19
Adaptation à une interface Soit une classe cliente qui invoque une méthode méthodeRequise() déclarée dans une interface. Supposez que vous avez trouvé une classe existante avec une méthode nommée par exemple méthodeUtile() capable de répondre aux besoins du client. Vous pouvez alors adapter cette classe au client en écrivant une classe qui étend ClasseExistante, implémente InterfaceRequise et redéfinit méthode-Requise() de sorte qu’elle délègue ses demandes à méthodeUtile(). La classe NouvelleClasse est un exemple de ADAPTER. Une instance de cette classe est une instance de InterfaceRequise. En d’autres termes, NouvelleClasse répond aux besoins du client. 20
Exemple
21
Exemple Oozinoz (1) Imaginez que vous travailliez avec un package qui simule le vol et le minutage de fusées comme celles fabriquées par Oozinoz. Ce package inclut un simulateur d’événements qui couvre les effets du lancement de plusieurs fusées, ainsi qu’une interface qui spécifie le comportement d’une fusée.
22
Exemple Oozinoz (2) Vous disposez d’une classe PhysicalRocket que vous voulez inclure dans la simulation. Cette classe possède des méthodes qui correspondent approximativement au comportement requis par le simulateur. Vous pouvez donc appliquer ADAPTER en dérivant de PhysicalRocket une sous-classe qui implémente l’interface RocketSim.
23
Adaptateurs de classe et d’objet Les conceptions précédentes sont des adaptateurs de classe, c’est-à-dire que l’adaptation procède de la dérivation de sousclasses. Dans une telle conception, la nouvelle classe adaptateur implémente l’interface désirée et étend une classe existante. Cette approche ne fonctionne pas toujours, notamment lorsque l’ensemble de méthodes que vous voulez adapter n’est pas spécifié dans une interface. Dans ce cas, vous pouvez créer un adaptateur d’objet, c’est-à-dire un adaptateur qui utilise la délégation plutôt que la dérivation de sous-classes. 24
Exemple
25
Exemple Oozinoz (1) Imaginez que le package de simulation fonctionne directement avec une classe Skyrocket, sans spécifier d’interface définissant les comportements nécessaires pour la simulation Dans cette conception-ci, le package com.oozinoz.simulation ne spécifie pas l’interface dont il a besoin pour modéliser une fusée. 26
Exemple Oozinoz (2) La classe Skyrocket utilise un modèle physique assez rudimentaire. Par exemple, elle part du principe que la fusée se consume entièrement à mesure que son carburant brûle. Supposez que vous vouliez appliquer le modèle plus sophistiqué offert par la classe PhysicalRocket d’Oozinoz. Pour adapter la logique de cette classe à la simulation, vous pourriez créer une classe OozinozSkyrocket en tant qu’adaptateur d’objet qui étend Skyrocket et utilise un objet PhysicalRocket, comme le montre la Figure sivante :
27
Exemple Oozinoz (3) Une fois complété, ce diagramme représentera la conception d’un adaptateur d’objet qui s’appuie sur les informations d’une classe existante pour satisfaire le besoin d’un client d’utiliser un objet Skyrocket.
28
En tant qu’adaptateur d’objet, la classe OozinozSkyrocket étend Skyrocket, et non PhysicalRocket. Cela permet à un objet OozinozSkyrocket de servir de substitut chaque fois que le client requiert un objet Skyrocket. La classe Skyrocket supporte la dérivation de sous-classes en définissant sa variable simTime comme étant protected.
Un objet OozinozSkyrocket est un objet Skyrocket, mais son travail est réalisé par transmission des appels à un objet PhysicalRocket. 29
Exercice 2 Imaginez que vous souhaitiez lister quelques fusées dans une table en utilisant une interface utilisateur Swing. Donnez la classe RocketTableModel qui adapte un tableau de fusées à l’interface attendue par TableModel. Exemple :
30
Pour résumer
31
Pour résumer
32
FACADE Un gros avantage de la POO est qu’elle permet d’éviter le développement de programmes monolithiques au code irrémédiablement enchevêtré. Dans un système OO, une application est, idéalement, une classe minimale qui unit les comportements d’autres classes groupées en kits d’outils réutilisables (packages). L’objectif du pattern FACADE est de fournir une interface simplifiant l’emploi des classes d’un package ou d’un sous-système.
33
Façades, utilitaires et démos Une classe de façade peut ne contenir que des méthodes statiques, auquel cas elle est appelée un utilitaire. Une démo est un exemple qui montre comment employer une classe ou un sous-système. A cet égard, la valeur des démos peut être vue comme étant égale à celle des façades.
34
Exemple HOME CINEMA
35
Pour regarder un film
Quand le fim est terminer il faut tout refaire à l’envers !! 36
Construire le façade de votre H C
37
Implémenter l’interface simplifier
38
Tester
39