Vous êtes sur la page 1sur 451

Rfrence

design patterns en Java


Les 23 modles de conception fondamentaux
Steven John Metsker William C. Wake

Les

Rseaux et tlcom Programmation

Gnie logiciel

Scurit Systme dexploitation

Les Design Patterns en Java


Les 23 modles de conception fondamentaux
Steven John Metsker et William C. Wake

Pearson Education France a apport le plus grand soin la ralisation de ce livre an de vous fournir une information complte et able. Cependant, Pearson Education France nassume de responsabilits, ni pour son utilisation, ni pour les contrefaons de brevets ou atteintes aux droits de tierces personnes qui pourraient rsulter de cette utilisation. Les exemples ou les programmes prsents dans cet ouvrage sont fournis pour illustrer les descriptions thoriques. Ils ne sont en aucun cas destins une utilisation commerciale ou professionnelle. Pearson Education France ne pourra en aucun cas tre tenu pour responsable des prjudices ou dommages de quelque nature que ce soit pouvant rsulter de lutilisation de ces exemples ou programmes. Tous les noms de produits ou marques cits dans ce livre sont des marques dposes par leurs propritaires respectifs.
Publi par Pearson Education France 47 bis, rue des Vinaigriers 75010 PARIS Tl. : 01 72 74 90 00 www.pearson.fr Mise en pages : TyPAO ISBN : 978-2-7440-4097-9 Copyright 2009 Pearson Education France Tous droits rservs ISBN original : 0-321-33302-0 Copyright 2006 by Addison-Wesley Tous droits rservs Titre original : Design Patterns in Java

Traduit de lamricain par Freenet Sofor ltd

Aucune reprsentation ou reproduction, mme partielle, autre que celles prvues larticle L. 122-5 2 et 3 a) du code de la proprit intellectuelle ne peut tre faite sans lautorisation expresse de Pearson Education France ou, le cas chant, sans le respect des modalits prvues larticle L. 122-10 dudit code. All rights reserved. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc.

Table des matires


Prface .............................................................................................................................. Conventions de codage ................................................................................................. Remerciements ............................................................................................................. Chapitre 1. Introduction ................................................................................................. Quest-ce quun pattern ? .............................................................................................. Quest-ce quun pattern de conception ? ...................................................................... Liste des patterns dcrits dans louvrage ................................................................ Java ............................................................................................................................... UML ............................................................................................................................. Exercices ....................................................................................................................... Organisation du livre ..................................................................................................... Oozinoz ......................................................................................................................... Rsum ......................................................................................................................... 1 1 2 3 3 4 5 7 7 8 9 10 11

Partie I Patterns dinterface Chapitre 2. Introduction aux interfaces ........................................................................ Interfaces et classes abstraites ....................................................................................... Interfaces et obligations ................................................................................................ Rsum ......................................................................................................................... Au-del des interfaces ordinaires .................................................................................. Chapitre 3. ADAPTER .................................................................................................... Adaptation une interface ............................................................................................ Adaptateurs de classe et dobjet ................................................................................... 15 16 17 19 19 21 21 25

IV

Table des matires

Adaptation de donnes pour un widget JTable .......................................................... Identication dadaptateurs .......................................................................................... Rsum ......................................................................................................................... Chapitre 4. FACADE ....................................................................................................... Faades, utilitaires et dmos ......................................................................................... Refactorisation pour appliquer FACADE ....................................................................... Rsum ......................................................................................................................... Chapitre 5. COMPOSITE .............................................................................................. Un composite ordinaire ................................................................................................. Comportement rcursif dans les objets composites ...................................................... Objets composites, arbres et cycles .............................................................................. Des composites avec des cycles .................................................................................... Consquences des cycles .............................................................................................. Rsum ......................................................................................................................... Chapitre 6. BRIDGE ....................................................................................................... Une abstraction ordinaire .............................................................................................. De labstraction au pattern BRIDGE ............................................................................. Des drivers en tant que BRIDGE ................................................................................... Drivers de base de donnes ........................................................................................... Rsum .........................................................................................................................

29 33 34 35 36 37 46 47 47 48 50 55 59 60 61 61 64 66 67 69

Partie II Patterns de responsabilit Chapitre 7. Introduction la responsabilit ................................................................. Responsabilit ordinaire ............................................................................................... Contrle de la responsabilit grce la visibilit ......................................................... Rsum ......................................................................................................................... Au-del de la responsabilit ordinaire .......................................................................... 73 73 75 77 77

Table des matires

Chapitre 8. SINGLETON ............................................................................................... Le mcanisme de SINGLETON ..................................................................................... Singletons et threads ..................................................................................................... Identication de singletons ........................................................................................... Rsum ......................................................................................................................... Chapitre 9. OBSERVER ................................................................................................. Un exemple classique : OBSERVER dans les interfaces utilisateurs ............................... Modle-Vue-Contrleur ................................................................................................ Maintenance dun objet Observable ......................................................................... Rsum ......................................................................................................................... Chapitre 10. MEDIATOR ............................................................................................... Un exemple classique : mdiateur de GUI ................................................................... Mdiateur dintgrit relationnelle ............................................................................... Rsum ......................................................................................................................... Chapitre 11. PROXY ....................................................................................................... Un exemple classique : proxy dimage ......................................................................... Reconsidration des proxies dimage ........................................................................... Proxy distant ................................................................................................................. Proxy dynamique .......................................................................................................... Rsum ......................................................................................................................... Chapitre 12. CHAIN OF RESPONSABILITY ............................................................. Une chane de responsabilits ordinaire ....................................................................... Refactorisation pour appliquer CHAIN OF RESPONSABILITY ................................... Ancrage dune chane de responsabilits ...................................................................... CHAIN OF RESPONSABILITY sans COMPOSITE ......................................................... Rsum ......................................................................................................................... Chapitre 13. FLYWEIGHT ............................................................................................ Immuabilit ................................................................................................................... Extraction de la partie immuable dun yweight ......................................................... Partage des objets yweight ......................................................................................... Rsum .........................................................................................................................

79 79 81 82 84 85 85 90 96 99 101 101 106 112 115 115 120 122 128 133 135 135 137 140 142 142 143 143 144 146 149

VI

Table des matires

Partie III Patterns de construction Chapitre 14. Introduction la construction .................................................................. Quelques ds de construction ..................................................................................... Rsum ......................................................................................................................... Au-del de la construction ordinaire ............................................................................. Chapitre 15. BUILDER ................................................................................................... Un objet constructeur ordinaire .................................................................................... Construction avec des contraintes ................................................................................. Un builder tolrant ........................................................................................................ Rsum ......................................................................................................................... Chapitre 16. FACTORY METHOD ............................................................................... Un exemple classique : des itrateurs ........................................................................... Identication de FACTORY METHOD ............................................................................. Garder le contrle sur le choix de la classe instancier ............................................... Application de FACTORY METHOD dans une hirarchie parallle ................................. Rsum ......................................................................................................................... Chapitre 17. ABSTRACT FACTORY ........................................................................... Un exemple classique : le kit de GUI ........................................................................... Classe FACTORY abstraite et pattern FACTORY METHOD .............................................. Packages et classes factory abstraites ........................................................................... Rsum ......................................................................................................................... Chapitre 18. PROTOTYPE ............................................................................................ Des prototypes en tant quobjets factory ...................................................................... Prototypage avec des clones ......................................................................................... Rsum ......................................................................................................................... Chapitre 19. MEMENTO ............................................................................................... Un exemple classique : dfaire une opration .............................................................. Dure de vie des mmentos .......................................................................................... 153 153 155 155 157 157 160 163 164 165 165 166 167 169 171 173 173 178 182 182 183 183 185 187 189 189 196

Table des matires

VII

Persistance des mmentos entre les sessions ................................................................ Rsum .........................................................................................................................

197 200

Partie IV Patterns dopration Chapitre 20. Introduction aux oprations ..................................................................... Oprations et mthodes ................................................................................................. Signatures ..................................................................................................................... Exceptions ..................................................................................................................... Algorithmes et polymorphisme .................................................................................... Rsum ......................................................................................................................... Au-del des oprations ordinaires ................................................................................ Chapitre 21. TEMPLATE METHOD ........................................................................... Un exemple classique : algorithme de tri ...................................................................... Compltion dun algorithme ......................................................................................... Hooks ............................................................................................................................ Refactorisation pour appliquer TEMPLATE METHOD .................................................... Rsum ......................................................................................................................... Chapitre 22. STATE ........................................................................................................ Modlisation dtats ...................................................................................................... Refactorisation pour appliquer STATE ......................................................................... Etats constants .............................................................................................................. Rsum ......................................................................................................................... Chapitre 23. STRATEGY ............................................................................................... Modlisation de stratgies ............................................................................................ Refactorisation pour appliquer STRATEGY ................................................................... Comparaison de STRATEGY et STATE .......................................................................... Comparaison de STRATEGY et TEMPLATE METHOD ..................................................... Rsum ......................................................................................................................... 203 203 205 205 206 208 209 211 211 215 218 219 221 223 223 227 231 233 235 236 238 242 243 243

VIII

Table des matires

Chapitre 24. COMMAND ............................................................................................... Un exemple classique : commandes de menus ............................................................. Emploi de COMMAND pour fournir un service ................................................................ Hooks ............................................................................................................................ COMMAND en relation avec dautres patterns .................................................................. Rsum ......................................................................................................................... Chapitre 25. INTERPRETER ........................................................................................ Un exemple de INTERPRETER ..................................................................................... Interprteurs, langages et analyseurs syntaxiques ........................................................ Rsum ......................................................................................................................... Partie V Patterns dextension Chapitre 26. Introduction aux extensions ..................................................................... Principes de la conception oriente objet ..................................................................... Le principe de substitution de Liskov ........................................................................... La loi de Demeter ......................................................................................................... Elimination des erreurs potentielles .............................................................................. Au-del des extensions ordinaires ................................................................................ Rsum ......................................................................................................................... Chapitre 27. DECORATOR ........................................................................................... Un exemple classique : ux dE/S et objets Writer ................................................... Enveloppeurs de fonctions ............................................................................................ DECORATOR en relation avec dautres patterns .............................................................. Rsum ......................................................................................................................... Chapitre 28. ITERATOR ................................................................................................ Itration ordinaire ......................................................................................................... Itration avec scurit inter-threads .............................................................................. Itration sur un objet composite ................................................................................... Ajout dun niveau de profondeur un numrateur ............................................... Enumration des feuilles ......................................................................................... Rsum .........................................................................................................................

245 245 248 249 251 252 253 254 265 266

269 269 270 271 273 273 274 277 277 285 292 293 295 295 297 303 310 311 313

Table des matires

IX

Chapitre 29. VISITOR .................................................................................................... Application de VISITOR .............................................................................................. Un VISITOR ordinaire .................................................................................................. Cycles et VISITOR ....................................................................................................... Risques de VISITOR .................................................................................................... Rsum .........................................................................................................................

315 315 318 323 328 330

Partie VI Annexes Annexe A. Recommandations ........................................................................................ Tirer le meilleur parti du livre ....................................................................................... Connatre ses classiques ............................................................................................... Appliquer les patterns ................................................................................................... Continuer dapprendre .................................................................................................. Annexe B. Solutions ......................................................................................................... Introduction aux interfaces ........................................................................................... Solution 2.1 ............................................................................................................. Solution 2.2 ............................................................................................................. Solution 2.3 ............................................................................................................. ADAPTER .................................................................................................................... Solution 3.1 ............................................................................................................. Solution 3.2 ............................................................................................................. Solution 3.3 ............................................................................................................. Solution 3.4 ............................................................................................................. Solution 3.5 ............................................................................................................. Solution 3.6 ............................................................................................................. FACADE ....................................................................................................................... Solution 4.1 ............................................................................................................. Solution 4.2 ............................................................................................................. Solution 4.3 ............................................................................................................. Solution 4.4 ............................................................................................................. 333 333 334 334 336 337 337 337 338 338 338 338 339 340 341 341 342 342 342 343 343 344

Table des matires

COMPOSITE ................................................................................................................ Solution 5.1 ............................................................................................................. Solution 5.2 ............................................................................................................. Solution 5.3 ............................................................................................................. Solution 5.4 ............................................................................................................. Solution 5.5 ............................................................................................................. Solution 5.6 ............................................................................................................. BRIDGE ....................................................................................................................... Solution 6.1 ............................................................................................................. Solution 6.2 ............................................................................................................. Solution 6.3 ............................................................................................................. Solution 6.4 ............................................................................................................. Solution 6.5 ............................................................................................................. Introduction la responsabilit ..................................................................................... Solution 7.1 ............................................................................................................. Solution 7.2 ............................................................................................................. Solution 7.3 ............................................................................................................. Solution 7.4 ............................................................................................................. SINGLETON ................................................................................................................ Solution 8.1 ............................................................................................................. Solution 8.2 ............................................................................................................. Solution 8.3 ............................................................................................................. Solution 8.4 ............................................................................................................ OBSERVER .................................................................................................................. Solution 9.1 ............................................................................................................. Solution 9.2 ............................................................................................................. Solution 9.3 ............................................................................................................. Solution 9.4 ............................................................................................................. Solution 9.5 ............................................................................................................. Solution 9.6 ............................................................................................................. Solution 9.7 ............................................................................................................. MEDIATOR .................................................................................................................. Solution 10.1 ........................................................................................................... Solution 10.2 ........................................................................................................... Solution 10.3 ........................................................................................................... Solution 10.4 ........................................................................................................... Solution 10.5 ...........................................................................................................

345 345 346 346 347 347 348 348 348 348 349 349 350 350 350 351 352 353 353 353 353 353 354 354 354 355 356 356 357 357 358 359 359 360 361 361 362

Table des matires

XI

PROXY ......................................................................................................................... Solution 11.1 ........................................................................................................... Solution 11.2 ........................................................................................................... Solution 11.3 ........................................................................................................... Solution 11.4 ........................................................................................................... Solution 11.5 ........................................................................................................... CHAIN OF RESPONSABILITY ................................................................................. Solution 12.1 ........................................................................................................... Solution 12.2 ........................................................................................................... Solution 12.3 ........................................................................................................... Solution 12.4 ........................................................................................................... Solution 12.5 ........................................................................................................... FLYWEIGHT ............................................................................................................... Solution 13.1 ........................................................................................................... Solution 13.2 ........................................................................................................... Solution 13.3 ........................................................................................................... Solution 13.4 ........................................................................................................... Introduction la construction ....................................................................................... Solution 14.1 ........................................................................................................... Solution 14.2 ........................................................................................................... Solution 14.3 ........................................................................................................... BUILDER ..................................................................................................................... Solution 15.1 ........................................................................................................... Solution 15.2 ........................................................................................................... Solution 15.3 ........................................................................................................... Solution 15.4 ........................................................................................................... FACTORY METHOD .................................................................................................. Solution 16.1 ........................................................................................................... Solution 16.2 ........................................................................................................... Solution 16.3 ........................................................................................................... Solution 16.4 ........................................................................................................... Solution 16.5 ........................................................................................................... Solution 16.6 ........................................................................................................... Solution 16.7 ........................................................................................................... ABSTRACT FACTORY ............................................................................................... Solution 17.1 ........................................................................................................... Solution 17.2 ...........................................................................................................

362 362 363 363 363 364 364 364 365 366 366 367 368 368 369 370 370 371 371 372 372 373 373 373 374 374 375 375 376 376 376 377 378 378 379 379 380

XII

Table des matires

Solution 17.3 ........................................................................................................... Solution 17.4 ........................................................................................................... Solution 17.5 ........................................................................................................... PROTOTYPE ................................................................................................................ Solution 18.1 ........................................................................................................... Solution 18.2 ........................................................................................................... Solution 18.3 ........................................................................................................... Solution 18.4 ........................................................................................................... MEMENTO .................................................................................................................. Solution 19.1 ........................................................................................................... Solution 19.2 ........................................................................................................... Solution 19.3 ........................................................................................................... Solution 19.4 ........................................................................................................... Solution 19.5 ........................................................................................................... Introduction aux oprations .......................................................................................... Solution 20.1 ........................................................................................................... Solution 20.2 ........................................................................................................... Solution 20.3 ........................................................................................................... Solution 20.4 ........................................................................................................... Solution 20.5 ........................................................................................................... TEMPLATE METHOD ................................................................................................ Solution 21.1 ........................................................................................................... Solution 21.2 ........................................................................................................... Solution 21.3 ........................................................................................................... Solution 21.4 ........................................................................................................... STATE ........................................................................................................................... Solution 22.1 ........................................................................................................... Solution 22.2 ........................................................................................................... Solution 22.3 ........................................................................................................... Solution 22.4 ........................................................................................................... STRATEGY .................................................................................................................. Solution 23.1 ........................................................................................................... Solution 23.2 ........................................................................................................... Solution 23.3 ........................................................................................................... Solution 23.4 ...........................................................................................................

380 381 381 382 382 383 383 384 384 384 385 385 386 386 387 387 387 388 388 388 389 389 389 390 390 390 390 390 391 391 392 392 392 392 393

Table des matires

XIII

COMMAND ................................................................................................................. Solution 24.1 ........................................................................................................... Solution 24.2 ........................................................................................................... Solution 24.3 ........................................................................................................... Solution 24.4 ........................................................................................................... Solution 24.5 ........................................................................................................... Solution 24.6 ........................................................................................................... INTERPRETER ............................................................................................................ Solution 25.1 396 Solution 25.2 ........................................................................................................... Solution 25.3 ........................................................................................................... Solution 25.4 ........................................................................................................... Introduction aux extensions .......................................................................................... Solution 26.1 398 Solution 26.2 ........................................................................................................... Solution 26.3 ........................................................................................................... Solution 26.4 ........................................................................................................... DECORATOR .............................................................................................................. Solution 27.1 399 Solution 27.2 ........................................................................................................... Solution 27.3 ........................................................................................................... Solution 27.4 ........................................................................................................... ITERATOR ................................................................................................................... Solution 28.1 401 Solution 28.2 ........................................................................................................... Solution 28.3 ........................................................................................................... Solution 28.4 ........................................................................................................... VISITOR ....................................................................................................................... Solution 29.1 403 Solution 29.2 ........................................................................................................... Solution 29.3 ........................................................................................................... Solution 29.4 ........................................................................................................... Solution 29.5 ........................................................................................................... Annexe C. Code source dOozinoz ............................................................................... Obtention et utilisation du code source ........................................................................ Construction du code dOozinoz ..................................................................................

393 393 393 395 395 396 396 396 397 397 397 398 398 398 399 399 400 401 401 401 402 402 402 403 403 403 404 404 405 405 406

XIV

Table des matires

Test du code avec JUnit ................................................................................................ Localiser les chiers ..................................................................................................... Rsum ......................................................................................................................... Annexe D. Introduction UML ..................................................................................... Classes .......................................................................................................................... Relations entre classes .................................................................................................. Interfaces ....................................................................................................................... Objets ............................................................................................................................ Etats .............................................................................................................................. Glossaire ............................................................................................................................ Bibliographie ..................................................................................................................... Index ..................................................................................................................................

406 406 407 409 409 412 414 414 416 417 425 427

Prface
Les patterns de conception sont des solutions de niveaux classe et mthode des problmes courants dans le dveloppement orient objet. Si vous tes dj un programmeur Java intermdiaire et souhaitez devenir avanc, ou bien si vous tes avanc mais navez pas encore tudi les patterns de conception, ce livre est pour vous. Il adopte une approche de cahier dexercices, chaque chapitre tant consacr un pattern particulier. En plus dexpliquer le pattern en question, chaque chapitre inclut un certain nombre dexercices vous demandant dexpliquer quelque chose ou de dvelopper du code pour rsoudre un problme. Nous vous recommandons vivement de prendre le temps deffectuer chaque exercice lorsque vous tombez dessus plutt que de lire le livre dune traite. En mettant en pratique vos connaissances au fur et mesure de leur acquisition, vous apprendrez mieux, mme si vous ne faites pas plus dun ou deux chapitres par semaine.

Conventions de codage
Le code des exemples prsents dans ce livre est disponible en ligne. Voyez lAnnexe C pour savoir comment lobtenir. Nous avons utilis le plus souvent un style cohrent avec les conventions de codage de Sun. Les accolades ont t omises lorsque ctait possible. Nous avons d faire quelques compromis pour nous adapter au format du livre. Pour respecter les colonnes troites, les noms de variables sont parfois plus courts que ceux que nous employons habituellement. Et pour viter les complications du contrle de code source, nous avons distingu les multiples versions dun mme chier en accolant un chiffre son nom (par exemple, ShowBallistics2). Vous devriez normalement utiliser le contrle de code source et travailler seulement avec la dernire version dune classe.

Prface

Remerciements
Nous tenons remercier le dfunt John Vlissides pour ses encouragements et ses recommandations concernant ce livre et dautres. John, diteur de la collection Software Patterns Series et coauteur de louvrage original Design Patterns, tait pour nous un ami et une inspiration. En plus de nous appuyer largement sur Design Patterns, nous nous sommes aussi inspirs de nombreux autres livres. Voyez pour cela la bibliographie en n douvrage. En particulier, The Unied Modeling Language User Guide (le Guide de lutilisateur UML) [Booch, Rambaugh, et Jacobsen 1999] donne une explication claire dUML, et JavaTM in a Nutshell (Java en concentr : Manuel de rfrence pour Java) [Flanagan 2005] constitue une aide concise et prcise sur Java. The Chemistry of Fireworks [Russell 2000] nous a servi de source dinformations pour laborer nos exemples pyrotechniques ralistes. Enn, nous sommes reconnaissants toute lquipe de production pour son travail acharn et son dvouement. Steve Metsker (Steve.Metsker@acm.org) Bill Wake (William.Wake@acm.org)

1
Introduction
Ce livre couvre le mme ensemble de techniques que louvrage de rfrence Design Patterns, dErich Gamma, Richard Helm, Ralph Johnson et John Vlissides [Gamma et al. 1995], et propose des exemples en Java. Il inclut de nombreux exercices conus pour vous aider dvelopper votre aptitude appliquer les patterns de conception dans vos programmes. Il sadresse aux dveloppeurs qui connaissent Java et souhaitent amliorer leurs comptences en tant que concepteurs.

Quest-ce quun pattern ?


Un pattern, ou modle, est un moyen daccomplir quelque chose, un moyen datteindre un objectif, une technique. Le principe est de compiler les mthodes prouves qui sappliquent de nombreux types defforts, tels que la fabrication daliments, dartices, de logiciels, ou autres. Dans nimporte quel art ou mtier nouveau en voie de maturation, ses pratiquants commencent, un moment donn, laborer des mthodes communes efcaces pour parvenir leurs buts et rsoudre des problmes dans diffrents contextes. Cette communaut invente aussi gnralement un jargon pour pouvoir discuter de son savoir-faire. Une partie de cette terminologie a trait aux modles, ou techniques tablies, permettant dobtenir certains rsultats. A mesure que cet art se dveloppe et que son jargon stoffe, les auteurs commencent jouer un rle important. En documentant les modles de cet art, ils contribuent standardiser son jargon et faire connatre ses techniques.

Les Design Patterns en Java

Les 23 modles de conception fondamentaux

Christopher Alexander a t un des premiers auteurs compiler les meilleures pratiques dun mtier en documentant ses modles. Son travail concerne larchitecture, celle des immeubles et non des logiciels. Dans A Pattern Language: Towns, Buildings Construction (Alexander, Ishikouwa, et Silverstein 1977), il dcrit des modles permettant de btir avec succs des immeubles et des villes. Cet ouvrage est puissant et a inuenc la communaut logicielle notamment en raison du sens quil donne au terme objectif (intent). Vous pourriez penser que les modles architecturaux servent principalement concevoir des immeubles. En fait, Alexander tablit clairement que leur objectif est de servir et dinspirer les gens qui occuperont les immeubles et les villes conus daprs ces modles. Son travail a montr que les modles sont un excellent moyen de saisir et de transmettre le savoir-faire et la sagesse dun art. Il prcise galement que comprendre et documenter correctement cet objectif est essentiel, philosophique et difcile. La communaut informatique a fait sienne cette approche en crant de nombreux ouvrages qui documentent des modles de dveloppement logiciel. Ces livres consignent les meilleures pratiques en matire de processus logiciels, danalyse logicielle, darchitecture de haut niveau, et de conception de niveau classe. Il en apparat de nouveaux chaque anne. Lisez les critiques et les commentaires de lecteurs pour faire un choix judicieux.

Quest-ce quun pattern de conception ?


Un pattern de conception (design pattern) est un modle qui utilise des classes et leurs mthodes dans un langage orient objet. Les dveloppeurs commencent souvent sintresser la conception seulement lorsquils matrisent un langage de programmation et crivent du code depuis longtemps. Il vous est probablement dj arriv de remarquer que du code crit par quelquun dautre semblait plus simple et plus efcace que le vtre, auquel cas vous avez d vous demander comment son dveloppeur tait parvenu une telle simplicit. Les patterns de conception interviennent un niveau au-dessus du code et indiquent typiquement comment atteindre un but en nutilisant que quelques classes. Un pattern reprsente une ide, et non une implmentation particulire. Dautres dveloppeurs ont dcouvert avant vous comment programmer efcacement dans les langages orients objet. Si vous souhaitez devenir un programmeur

Chapitre 1

Introduction

Java avanc, vous devriez tudier les patterns de conception, surtout ceux de ce livre les mmes que ceux expliqus dans Design Patterns. Louvrage Design Patterns dcrit vingt-trois patterns de conception (pour plus de dtails, voir section suivante). De nombreux autres livres ont suivi sur le sujet, aussi dnombre-t-on au moins cent patterns qui valent la peine dtre connus. Les vingttrois patterns recenss par Gamma, Helm, Johnson et Vlissides ne sont pas forcment les plus importants, mais ils sont nanmoins proches du haut de la liste. Ces auteurs ont donc bien choisi et les patterns quils documentent valent certainement la peine que vous les appreniez. Ils vous serviront de rfrence lorsque commencerez tudier les patterns exposs par dautres sources.
Liste des patterns dcrits dans louvrage

Patterns dinterface

ADAPTER (17) fournit linterface quun client attend en utilisant les services dune classe dont linterface est diffrente. FACADE (33) fournit une interface simpliant lemploi dun sous-systme. COMPOSITE (47) permet aux clients de traiter de faon uniforme des objets individuels et des compositions dobjets. BRIDGE (63) dcouple une classe qui sappuie sur des oprations abstraites de limplmentation de ces oprations, permettant ainsi la classe et son implmentation de varier indpendamment.
Patterns de responsabilit

SINGLETON (81) garantit quune classe ne possde quune seule instance, et fournit un point daccs global celle-ci. OBSERVER (87) dnit une dpendance du type un--plusieurs (1,n) entre des objets de manire ce que lorsquun objet change dtat, tous les objets dpendants en soient notis et soient actualiss an de pouvoir ragir conformment. MEDIATOR (103) dnit un objet qui encapsule la faon dont un ensemble dobjets interagissent. Cela promeut un couplage lche, vitant aux objets davoir se rfrer explicitement les uns aux autres, et permet de varier leur interaction indpendamment.

Les Design Patterns en Java

Les 23 modles de conception fondamentaux

PROXY (117) contrle laccs un objet en fournissant un intermdiaire pour cet objet. CHAIN OF RESPONSABILITY (137) vite de coupler lmetteur dune requte son rcepteur en permettant plus dun objet dy rpondre. FLYWEIGHT (145) utilise le partage pour supporter efcacement un grand nombre dobjets forte granularit.
Patterns de construction

BUILDER (159) dplace la logique de construction dun objet en-dehors de la classe instancier, typiquement pour permettre une construction partielle ou pour simplier lobjet. FACTORY METHOD (167) laisse un autre dveloppeur dnir linterface permettant de crer un objet, tout en gardant un contrle sur le choix de la classe instancier. ABSTRACT FACTORY (175) permet la cration de familles dobjets ayant un lien ou interdpendants. PROTOTYPE (187) fournit de nouveaux objets par la copie dun exemple. MEMENTO (193) permet le stockage et la restauration de ltat dun objet.
Patterns dopration

TEMPLATE METHOD (217) implmente un algorithme dans une mthode, laissant dautres classes le soin de dnir certaines tapes de lalgorithme. STATE (229) distribue la logique dpendant de ltat dun objet travers plusieurs classes qui reprsentent chacune un tat diffrent.
STRATEGY (241) encapsule des approches, ou stratgies, alternatives dans des classes distinctes qui implmentent chacune une opration commune.

COMMAND (251) encapsule une requte en tant quobjet, de manire pouvoir paramtrer des clients au moyen de divers types de requtes (de le dattente, de temps ou de journalisation) et de permettre un client de prparer un contexte spcial dans lequel mettre la requte. INTERPRETER (261) permet de composer des objets excutables daprs un ensemble de rgles de composition que vous dnissez.

Chapitre 1

Introduction

Patterns dextension

DECORATOR (287) permet de composer dynamiquement le comportement dun objet. ITERATOR (305) fournit un moyen daccder de faon squentielle aux lments dune collection. VISITOR (325) permet de dnir une nouvelle opration pour une hirarchie sans changer ses classes.

Java
Les exemples de ce livre utilisent Java, le langage orient objet (OO) dvelopp par Sun. Ce langage, ses bibliothques et ses outils associs forment une suite de produits pour le dveloppement et la gestion de systmes aux architectures multiniveaux et orientes objet. Limportance de Java tient en partie au fait quil sagit dun langage de consolidation, cest--dire conu pour intgrer les points forts des langages prcdents. Cette consolidation est la cause de son succs et garantit que les langages futurs tendront sinscrire dans sa continuit au lieu de sen loigner radicalement. Votre investissement dans Java ne perdra assurment pas de sa valeur, quel que soit le langage qui lui succde. Les patterns de Design Patterns sappliquent Java, car, comme Smalltalk, C++ et C#, ils se fondent sur un paradigme classe/instance. Java ressemble beaucoup plus Smalltalk et C++ qu Prolog ou Self par exemple. Mme sil ne faut pas ngliger limportance de paradigmes concurrents, le paradigme classe/instance constitue une avance concrte en informatique applique. Le prsent livre emploie Java en raison de sa popularit et parce que son volution suit le chemin des langages que nous utiliserons dans les annes venir.

UML
Lorsque les solutions des exercices contiennent du code, ce livre utilise Java. Mais nombre dexercices vous demandent de dessiner un diagramme illustrant les relations entre des classes, des packages et dautres lments. Vous pouvez choisir la notation que vous prfrez, mais sachez que ce livre utilise la notation UML (Unied Modeling Language). Mme si vous la connaissez dj, il peut tre utile

Les Design Patterns en Java

Les 23 modles de conception fondamentaux

davoir une rfrence porte de main. Vous pouvez consulter deux ouvrages de qualit : The Unied Modeling Language User Guide (le Guide de lutilisateur UML) [Booch, Rumbaugh, et Jacobsen 1999] et UML Distilled [Fowler et Scott 2003]. Les connaissances minimales dont vous avez besoin pour ce livre sont donnes dans lAnnexe D consacre UML.

Exercices
Mme si vous lisez de nombreux ouvrages sur un sujet, vous naurez le sentiment de le matriser vraiment quen le pratiquant. Tant que vous nappliquerez pas concrtement les connaissances acquises, certaines subtilits et approches alternatives vous chapperont. Le seul moyen de gagner de lassurance avec les patterns de conception est de les appliquer dans le cadre dexercices pratiques. Le problme lorsque lon apprend en faisant est que lon peut causer des dgts. Vous ne pouvez pas appliquer les patterns de conception dans du code en production si vous ne les matrisez pas. Mais il faut bien que vous commenciez les appliquer pour acqurir ce savoir-faire. La solution est de vous familiariser avec les patterns au travers dexemples de problmes, o vos erreurs seront sans consquence mais instructives. Chaque chapitre de ce livre dbute par une courte introduction puis prsente progressivement une srie dexercices. Lorsque vous avez trouv une solution, vous pouvez la comparer aux rponses proposes dans lAnnexe B. Il se peut que la solution du livre adopte une approche diffrente de la vtre, vous faisant voir les choses sous une autre perspective. Vous ne pouvez probablement pas prvoir le temps quil vous faudra pour trouver les rponses aux exercices. Si vous consultez dautres livres, travaillez avec un collgue et crivez des chantillons de code pour vrier votre solution, cest parfait ! Vous ne regretterez pas lnergie et le temps investis. Un avertissement : si vous vous contentez de lire les solutions immdiatement aprs avoir lu un exercice, vous ne tirerez pas un grand enseignement de ce livre. Ces solutions ne vous seront daucune utilit si vous nlaborez pas dabord les vtres pour pouvoir ensuite les leur comparer et tirer les leons de vos erreurs.

Chapitre 1

Introduction

Organisation du livre
Il existe de nombreuses faons dorganiser et de classer les patterns de conception. Vous pourriez les organiser en fonction de leurs similitudes sur le plan structurel, ou bien suivre lordre de Design Patterns. Mais laspect le plus important dun pattern est son objectif, cest--dire la valeur potentielle lie son application. Le prsent livre organise les vingt-trois patterns de Design Patterns en fonction de leur objectif. Reste ensuite dterminer comment catgoriser ces objectifs. Nous sommes partis du principe que lobjectif dun pattern de conception peut gnralement tre exprim comme tant le besoin daller plus loin que les fonctionnalits ordinaires intgres Java. Par exemple, Java offre un large support pour la dnition des interfaces implmentes par les classes. Mais si vous disposez dj dune classe dont vous aimeriez modier linterface pour quelle corresponde aux exigences dun client, vous pourriez dcider dappliquer le pattern ADAPTER. Lobjectif de ce pattern est de vous aider complmenter les fonctionnalits dinterface intgres Java. Ce livre regroupe les patterns de conception en cinq catgories que voici : 1. Interfaces. 2. Responsabilit. 3. Construction. 4. Oprations. 5. Extensions. Ces cinq catgories correspondent aux cinq parties du livre. Chaque partie dbute par un chapitre qui prsente et remet en question les fonctionnalits Java lies au type dobjectif dont il est question. Par exemple, le premier chapitre de la Partie I traite des interfaces Java ordinaires. Il vous amne rchir sur la structure des interfaces Java, notamment en les comparant aux classes abstraites. Les autres chapitres de cette partie dcrivent les patterns qui ont pour principal objectif de dnir une interface, cest--dire lensemble des mthodes quun client peut appeler partir dun fournisseur de services. Chacun deux rpond un besoin que les interfaces Java ne peuvent satisfaire elles seules.

10

Les Design Patterns en Java

Les 23 modles de conception fondamentaux

Ce classement des patterns par objectifs ne signie pas que chaque pattern supporte seulement un type dobjectif. Lorsquil en supporte plusieurs, il fait lobjet dun chapitre entier dans la premire partie laquelle il sapplique puis il est mentionn brivement dans les autres parties concernes. Le Tableau 1.1 illustre la catgorisation sous-jacente lorganisation du livre.
Tableau 1.1 : Une catgorisation des patterns par objectifs

Objectif Interfaces Responsabilit Construction Oprations Extensions

Patterns

ADAPTER, FACADE, COMPOSITE, BRIDGE SINGLETON, OBSERVER, MEDIATOR, PROXY, CHAIN OF RESPONSIBILITY, FLYWEIGHT BUILDER, FACTORY METHOD, ABSTRACT FACTORY, PROTOTYPE, MEMENTO TEMPLATE METHOD, STATE, STRATEGY, COMMAND, INTERPRETER DECORATOR, ITERATOR, VISITOR

Nous esprons que ce classement vous amnera vous interroger. Pensez-vous aussi que SINGLETON a trait la responsabilit, et non la construction ? COMPOSITE est-il rellement un pattern dinterface ? Toute catgorisation est subjective. Mais vous conviendrez certainement que le fait de rchir lobjectif des patterns et la faon de les appliquer est un exercice trs utile.

Oozinoz Les exercices de ce livre citent tous des exemples dOozinoz Fireworks, une entreprise ctive qui fabrique et vend des pices pour feux dartice et organise des vnements pyrotechniques. Vous pouvez vous procurer le code de ces exemples ladresse www.oozinoz.com. Pour en savoir plus sur la compilation et le test du code, voyez lAnnexe C.

Chapitre 1

Introduction

11

Rsum Les patterns de conception distillent une sagesse vieille de quelques dizaines dannes qui tablit un jargon standard, permettant aux dveloppeurs de nommer les concepts quils appliquent. Ceux abords dans louvrage de rfrence Design Patterns font partie des patterns de niveau classe les plus utiles et mritent que vous les appreniez. Le prsent livre reprend ces patterns mais utilise Java et ses bibliothques pour ses exemples et exercices. En ralisant les exercices proposs, vous apprendrez reconnatre et appliquer une part importante de la sagesse de la communaut logicielle.

I
Patterns dinterface

2
Introduction aux interfaces
Pour parler de manire abstraite, linterface dune classe est lensemble des mthodes et champs de la classe auxquels des objets dautres classes sont autoriss accder. Elle constitue gnralement un engagement que les mthodes accompliront lopration signie par leur nom et tel que spcie par les commentaires, les tests et autres documentations du code. Limplmentation dune classe est le code contenu dans ses mthodes. Java fait du concept dinterface une structure distincte, sparant expressment linterface ce quun objet doit faire de limplmentation comment un objet remplit cet engagement. Les interfaces Java permettent plusieurs classes doffrir la mme fonctionnalit et une mme classe dimplmenter plusieurs interfaces. Plusieurs patterns de conception emploient les fonctionnalits intgres Java. Par exemple, vous pourriez utiliser une interface pour adapter linterface dune classe an de rpondre aux besoins dun client en appliquant le pattern ADAPTER. Mais avant daborder certaines notions avances, il peut tre utile de sassurer que vous matrisez les fonctionnalits de base, commencer par les interfaces.

16

Partie I

Patterns dinterface

Interfaces et classes abstraites


Le livre original Design Patterns [Gamma et al. 1995] mentionne frquemment lemploi de classes abstraites mais pas du tout lemploi dinterfaces. La raison en est que les langages C++ et Smalltalk, sur lesquels il sappuie pour ses exemples, ne possdent pas une telle structure. Cela ne remet toutefois pas en cause lutilit de ce livre pour les dveloppeurs Java, tant donn que les interfaces Java sont assez semblables aux classes abstraites. Exercice 2.1 Enumrez trois diffrences entre les classes abstraites et les interfaces Java.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

Si les interfaces nexistaient pas, vous pourriez utiliser la place des classes abstraites, comme dans C++. Les interfaces jouent toutefois un rle essentiel dans le dveloppement dapplications multiniveaux, ce qui justie certainement leur statut particulier de structure distincte. Considrez la dnition dune interface que les classes de simulation de fuse doivent implmenter. Les ingnieurs conoivent toutes sortes de fuses, quelles soient combustible solide ou liquide, avec des caractristiques balistiques trs diverses. Indpendamment de sa composition, la simulation dune fuse doit fournir des chiffres pour la pousse (thrust) et la masse (mass). Voici le code quutilise Oozinoz pour dnir linterface de simulation de fuse :
package com.oozinoz.simulation; public interface RocketSim { abstract double getMass(); public double getThrust(); void setSimTime(double t); }

Chapitre 2

Introduction aux interfaces

17

Exercice 2.2 Parmi les afrmations suivantes, lesquelles sont vraies ? A. Les mthodes de linterface RocketSim sont toutes trois abstraites, mme si seulement getMass() dclare cela explicitement. B. Les trois mthodes de linterface sont publiques, mme si seulement getThrust() dclare cela explicitement. C. Linterface est dclare public interface, mais elle serait publique mme si le mot cl public tait omis. D. Il est possible de crer une autre interface, par exemple RocketSimSolid, qui tende RocketSim. E. Toute interface doit comporter au moins une mthode. F. Une interface peut dclarer des champs dinstance quune classe dimplmentation doit galement dclarer. G. Bien quil ne soit pas possible dinstancier une interface, une interface peut dclarer des mthodes constructeurs dont la signature sera donne par une classe dimplmentation.

Interfaces et obligations
Un avantage important des interfaces Java est quelles limitent linteraction entre les objets. Cette limitation savre tre un soulagement. En effet, une classe qui implmente une interface peut subir des changements considrables dans sa faon de remplir le contrat dni par linterface sans que cela affecte aucunement ses clients. Un dveloppeur qui cre une classe implmentant RocketSim a pour tche dcrire les mthodes getMass() et getThrust() qui retournent les mesures de performance dune fuse. Autrement dit, il doit remplir le contrat de ces mthodes. Parfois, les mthodes dsignes par une interface nont aucune obligation de fournir un service lappelant. Dans certains cas, la classe dimplmentation peut mme ignorer lappel, implmentant une mthode avec un corps vide.

18

Partie I

Patterns dinterface

Exercice 2.3 Donnez un exemple dinterface avec des mthodes nimpliquant aucune responsabilit pour la classe dimplmentation de retourner une valeur ou daccomplir une quelconque action pour le compte de lappelant. Si vous crez une interface qui spcie un ensemble de mthodes de notication, vous pourriez envisager dutiliser une classe stub, cest--dire une classe qui implmente linterface avec des mthodes ne faisant rien. Les dveloppeurs peuvent driver des sous-classes de la classe stub, en rednissant uniquement les mthodes de linterface qui sont importantes pour leur application. La classe WindowAdapter dans java.awt.event est un exemple dune telle classe, comme illustr Figure 2.1 (pour une introduction rapide UML, voyez lAnnexe D). Cette classe implmente toutes les mthodes de linterface WindowListener mais les implmentations sont vides ; ces mthodes ne contiennent aucune instruction.

interface WindowListener

WindowAdapter

windowActivated() windowClosed() windowClosing() windowDeactivated() windowDeiconified() windowIconified() windowOpened() windowStateChanged() windowGainedFocus() windowLostFocus()

windowActivated() windowClosed() windowClosing() windowDeactivated() windowDeiconified() windowIconified() windowOpened() windowStateChanged() windowGainedFocus() windowLostFocus()

Figure 2.1
La classe WindowAdapter facilite lenregistrement de listeners pour les vnements de fentre en vous permettant dignorer ceux qui ne vous intressent pas.

Chapitre 2

Introduction aux interfaces

19

En plus de dclarer des mthodes, une interface peut dclarer des constantes. Dans lexemple suivant, ClassificationConstants dclare deux constantes auxquelles les classes implmentant cette interface auront accs :
public interface ClassificationConstants { static final int CONSUMER = 1; static final int DISPLAY = 2; }

Une autre diffrence notable existe entre les interfaces et les classes abstraites. Tout en dclarant quelle tend (extends) une autre classe, une classe peut aussi dclarer quelle implmente (implements) une ou plusieurs interfaces.

Rsum
La puissance des interfaces rside dans le fait quelles stipulent ce qui est attendu et ce qui ne lest pas en matire de collaboration entre classes. Elles sont semblables aux classes purement abstraites en ce quelles dnissent des attentes mais ne les implmentent pas. Matriser la fois les concepts et les dtails de lapplication des interfaces Java demande du temps, mais le sacrice en vaut la peine. Cette structure puissante est au cur de nombreuses conceptions robustes et de plusieurs patterns de conception.

Au-del des interfaces ordinaires


Vous pouvez simplier et renforcer vos conceptions grce une application approprie des interfaces Java. Parfois, cependant, la conception dune interface doit dpasser sa dnition et son utilisation ordinaires.
Si vous envisagez de Adapter linterface dune classe pour quelle corresponde linterface attendue par un client Fournir une interface simple pour un ensemble de classes Dnir une interface qui sapplique la fois des objets individuels et des groupes dobjets Dcoupler une abstraction de son implmentation de sorte que les deux puissent varier indpendamment Appliquez le pattern

ADAPTER FACADE COMPOSITE BRIDGE

20

Partie I

Patterns dinterface

Lobjectif de chaque pattern de conception est de rsoudre un problme dans un certain contexte. Les patterns dinterface conviennent dans des contextes o vous avez besoin de dnir ou de rednir laccs aux mthodes dune classe ou dun groupe de classes. Par exemple, lorsque vous disposez dune classe qui accomplit un service ncessaire, mais dont les noms de mthodes ne correspondent pas aux attentes dun client, vous pouvez appliquer le pattern ADAPTER.

3
ADAPTER
Un objet est un client lorsquil a besoin dappeler votre code. Dans certains cas, votre code existe dj et le dveloppeur peut crer le client de manire ce quil utilise les interfaces de vos objets. Dans dautres, le client peut tre dvelopp indpendamment de votre code. Par exemple, un programme de simulation de fuse pourrait tre conu pour utiliser les informations techniques que vous fournissez, mais une telle simulation aurait sa propre dnition du comportement que doit avoir une fuse. Si une classe existante est en mesure dassurer les services requis par un client mais que ses noms de mthodes diffrent, vous pouvez appliquer le pattern ADAPTER. Lobjectif du pattern ADAPTER est de fournir linterface quun client attend en utilisant les services dune classe dont linterface est diffrente.

Adaptation une interface


Le dveloppeur dun client peut avoir prvu les situations o vous aurez besoin dadapter votre code au sien. Cela est vident sil a fourni une interface qui dnit les services dont le code client a besoin, comme dans lexemple de la Figure 3.1. Une classe cliente invoque une mthode mthodeRequise() dclare dans une interface. Supposez que vous avez trouv une classe existante avec une mthode nomme par exemple mthodeUtile() capable de rpondre aux besoins du client. Vous pouvez alors adapter cette classe au client en crivant une classe qui tend ClasseExistante, implmente InterfaceRequise et rednit mthodeRequise() de sorte quelle dlgue ses demandes mthodeUtile(). La classe NouvelleClasse est un exemple de ADAPTER. Une instance de cette classe est une instance de InterfaceRequise. En dautres termes, NouvelleClasse rpond aux besoins du client.

22

Partie I

Patterns dinterface

Figure 3.1
Lorsque le dveloppeur du code client dnit prcisment les besoins du client, vous pouvez remplir le contrat dni par linterface en adaptant le code existant.

Client

interface InterfaceRequise ClasseExistante

mthodeRequise()

mthodeUtile()

NouvelleClasse

mthodeRequise()

Pour prendre un exemple plus concret, imaginez que vous travailliez avec un package qui simule le vol et le minutage de fuses comme celles fabriques par Oozinoz. Ce package inclut un simulateur dvnements qui couvre les effets du lancement de plusieurs fuses, ainsi quune interface qui spcie le comportement dune fuse. La Figure 3.2 illustre ce package. Vous disposez dune classe PhysicalRocket que vous voulez inclure dans la simulation. Cette classe possde des mthodes qui correspondent approximativement au comportement requis par le simulateur. Vous pouvez donc appliquer ADAPTER en drivant de PhysicalRocket une sous-classe qui implmente linterface RocketSim. La Figure 3.3 illustre partiellement cette conception. La classe PhysicalRocket contient les informations dont le simulateur a besoin, mais ses mthodes ne correspondent pas exactement celles que le programme de simulation dclare dans linterface RocketSim. Cette diffrence tient au fait que le simulateur possde une horloge interne et actualise occasionnellement les objets simuls en invoquant une mthode setSimTime(). Pour adapter la classe PhysicalRocket aux exigences du simulateur, un objet OozinozRocket pourrait utiliser une variable dinstance time et la passer aux mthodes de la classe PhysicalRocket lorsque ncessaire.

Chapitre 3

ADAPTER

23

com.oozinoz.simulation

EventSim

interface RocketSim

getMass():double

Figure 3.2
Le package Simulation dnit clairement ses exigences pour simuler le vol dune fuse.

PhysicalRocket interface RocketSim PhysicalRocket( burnArea:double, burnRate:double, fuelMass:double, totalMass:double) getBurnTime():double getMass(t:double):double getThrust(t:double):double

getMass():double getThrust():double setSimTime(t:double)

OozinozRocket

Figure 3.3
Une fois complt, ce diagramme reprsentera la conception dune classe qui adapte la classe PhysicalRocket pour rpondre aux exigences de linterface RocketSim.

24

Partie I

Patterns dinterface

Exercice 3.1 Compltez le diagramme de la Figure 3.3 en faisant en sorte que la classe OozinozRocket permette un objet PhysicalRocket de prendre part une simulation en tant quobjet RocketSim. Partez du principe que vous ne pouvez modier ni RocketSim ni PhysicalRocket.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.


Le code de PhysicalRocket est un peu complexe car il runit toutes les caractristiques physiques dont se sert Oozinoz pour modliser une fuse. Mais cest exactement la logique que nous voulons rutiliser. La classe adaptateur OozinozRocket traduit simplement les appels pour utiliser les mthodes de sa super-classe. Le code de cette nouvelle sous-classe pourrait ressembler ce qui suit :
package com.oozinoz.firework; import com.oozinoz.simulation.*; public class OozinozRocket extends PhysicalRocket implements RocketSim { private double time; public OozinozRocket( double burnArea, double burnRate, double fuelMass, double totalMass) { super(burnArea, burnRate, fuelMass, totalMass); } public double getMass() { // Exercice ! } public double getThrust() { // Exercice ! } public void setSimTime(double time) { this.time = time; } }

Exercice 3.2 Compltez le code de la classe OozinozRocket en dnissant les mthodes getMass() et getThrust().

Chapitre 3

ADAPTER

25

Lorsquun client dnit ses attentes dans une interface, vous pouvez appliquer ADAPTER en fournissant une classe qui implmente cette interface et tend une classe existante. Il se peut aussi que vous puissiez appliquer ce pattern mme en labsence dune telle interface, auquel cas il convient dutiliser un adaptateur dobjet.

Adaptateurs de classe et dobjet


Les conceptions des Figures 3.1 et 3.3 sont des adaptateurs de classe, cest--dire que ladaptation procde de la drivation de sous-classes. Dans une telle conception, la nouvelle classe adaptateur implmente linterface dsire et tend une classe existante. Cette approche ne fonctionne pas toujours, notamment lorsque lensemble de mthodes que vous voulez adapter nest pas spci dans une interface. Dans ce cas, vous pouvez crer un adaptateur dobjet, cest--dire un adaptateur qui utilise la dlgation plutt que la drivation de sous-classes. La Figure 3.4 illustre cette conception (comparez-la aux diagrammes prcdents).

Client

ClasseRequise

ClasseExistante

mthodeRequise()

mthodeUtile()

NouvelleClasse

mthodeRequise()

Figure 3.4
Vous pouvez crer un adaptateur dobjet en drivant la sous-classe dont vous avez besoin et en remplissant les contrats des mthodes en vous appuyant sur un objet dune classe existante.

26

Partie I

Patterns dinterface

La classe NouvelleClasse est un exemple de ADAPTER. Une instance de cette classe est une instance de ClasseRequise. En dautres termes, NouvelleClasse rpond aux besoins du client. Elle peut adapter la classe ClasseExistante pour satisfaire le client en utilisant une instance de cette classe. Pour prendre un exemple plus concret, imaginez que le package de simulation fonctionne directement avec une classe Skyrocket, sans spcier dinterface dnissant les comportements ncessaires pour la simulation. La Figure 3.5 illustre cette classe.
Figure 3.5
Dans cette conception-ci, le package com.oozinoz.simulation ne spcie pas linterface dont il a besoin pour modliser une fuse.
com.oozinoz.simulation

EventSim

Skyrocket

Skyrocket( mass:double, thrust:double burnTime:double) getMass():double getThrust():double setSimTime(t:double)

La classe Skyrocket utilise un modle physique assez rudimentaire. Par exemple, elle part du principe que la fuse se consume entirement mesure que son carburant brle. Supposez que vous vouliez appliquer le modle plus sophistiqu offert par la classe PhysicalRocket dOozinoz. Pour adapter la logique de cette classe la simulation, vous pourriez crer une classe OozinozSkyrocket en tant quadaptateur dobjet qui tend Skyrocket et utilise un objet PhysicalRocket, comme le montre la Figure 3.6.

Chapitre 3

ADAPTER

27

Skyrocket #simTime:double ... Skyrocket( mass:double, thrust:double burnTime:double) getMass():double getThrust():double setSimTime(t:double)

PhysicalRocket

PhysicalRocket( burnArea:double, burnRate:double, fuelMass:double, totalMass:double) getBurnTime():double getMass(t:double):double getThrust(t:double):double

OozinozSkyrocket

Figure 3.6
Une fois complt, ce diagramme reprsentera la conception dun adaptateur dobjet qui sappuie sur les informations dune classe existante pour satisfaire le besoin dun client dutiliser un objet Skyrocket.

En tant quadaptateur dobjet, 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 drivation de sous-classes en dnissant sa variable simTime comme tant protected. Exercice 3.3 Compltez le diagramme de la Figure 3.6 en faisant en sorte que des objets OozinozSkyrocket puissent servir dobjets Skyrocket.

28

Partie I

Patterns dinterface

Le code de la classe OozinozSkyrocket pourrait ressembler ce qui suit :


package com.oozinoz.firework; import com.oozinoz.simulation.*; public class OozinozSkyrocket extends Skyrocket { private PhysicalRocket rocket; public OozinozSkyrocket(PhysicalRocket r) { super( r.getMass(0), r.getThrust(0), r.getBurnTime()); rocket = r; } public double getMass() { return rocket.getMass(simTime); } public double getThrust() { return rocket.getThrust(simTime); } }

La classe OozinozSkyrocket vous permet de fournir un objet OozinozSkyrocket chaque fois que le package requiert un objet Skyrocket. En gnral, les adaptateurs dobjet rsolvent, partiellement du moins, le problme pos par ladaptation dun objet une interface qui na pas t expressment dnie. Exercice 3.4 Citez une raison pour laquelle la conception dadaptateur dobjet utilise pa r la classe OozinozSkyrocket est plus fragile que lapproche avec adaptateur de classe.

Ladaptateur dobjet pour la classe Skyrocket est une conception plus risque que ladaptateur de classe qui implmente linterface RocketSim. Mais il ne faut pas trop se plaindre. Au moins, aucune mthode na t dnie comme tant final, ce qui nous aurait empchs de la rednir.

Chapitre 3

ADAPTER

29

Adaptation de donnes pour un widget JTable


Lafchage de donnes sous forme de table donne lieu un exemple courant dadaptateur dobjet. Swing fournit le widget JTable pour afcher des tables. Les concepteurs de ce widget ne savaient naturellement pas quelles donnes il servirait afcher. Aussi, plutt que de coder en dur certaines structures de donnes, ils ont prvu une interface appele TableModel (voir Figure 3.7) dont dpend le fonctionnement de JTable. Il vous revient ensuite de crer un adaptateur pour que vos donnes soient conformes TableModel.
Figure 3.7
La classe JTable est un composant Swing qui afche dans une table de GUI les donnes dune implmentation de TableModel.
JTable <<interface>> TableModel addTableModelListener() getColumnClass() getColumnCount() getColumnName() getRowCount() getValueAt() isCellEditable() removeTableModelListener() setValueAt()

Nombre des mthodes de TableModel suggrent la possibilit dune implmentation par dfaut. Heureusement, le JDK (Java Development Kit) inclut une classe abstraite qui fournit des implmentations par dfaut pour toutes les mthodes de cette interface lexception de celles qui sont trs spciques un domaine. La Figure 3.8 illustre cette classe. Imaginez que vous souhaitiez lister quelques fuses dans une table en utilisant une interface utilisateur Swing. Comme le montre la Figure 3.9, vous pourriez crer une classe RocketTableModel qui adapte un tableau de fuses linterface attendue par TableModel. La classe RocketTableModel doit tendre AbstractTableModel puisque cette dernire est une classe et non une interface. Lorsque linterface cible de ladaptation est supporte par une classe abstraite que vous souhaitez utiliser, vous devez

30

Partie I

Patterns dinterface

Figure 3.8
La classe AbstractTableModel prvoit des implmentations par dfaut pour presque toutes les mthodes de TableModel.
javax.swing.table

<<interface>> TableModel

AbstractTableModel

getColumnCount() getRowCount() getValueAt()

<<interface>> TableModel Rocket

AbstractTableModel

getName():String getPrice():Dollars getApogee():double

RocketTableModel #rockets[]:Rocket #columnNames[]:String RocketTableModel(rockets[]:Rocket) getColumnCount() getColumnName(i:int) getRowCount() getValueAt(row:int,col:int)

Figure 3.9
La classe RocketTableModel adapte linterface TableModel la classe Rocket du domaine Oozinoz.

Chapitre 3

ADAPTER

31

crer un adaptateur dobjet. Dans notre exemple, une autre raison qui justie de ne pas recourir un adaptateur de classe est que RocketTableModel nest ni un type ni un sous-type de Rocket. Lorsquune classe adaptateur doit tirer ses informations de plusieurs objets, elle est habituellement implmente en tant quadaptateur dobjet. Retenez la diffrence : un adaptateur de classe tend une classe existante et implmente une interface cible tandis quun adaptateur dobjet tend une classe cible et dlgue une classe existante. Une fois la classe RocketTableModel cre, vous pouvez facilement afcher des informations sur les fuses dans un objet Swing JTable, comme illustr Figure 3.10.
Figure 3.10
Une instance de JTable contenant des donnes sur les fuses.

package app.adapter; import javax.swing.table.*; import com.oozinoz.firework.Rocket; public class RocketTableModel extends AbstractTableModel { protected Rocket[] rockets; protected String[] columnNames = new String[] { "Name", "Price", "Apogee" }; public RocketTableModel(Rocket[] rockets) { this.rockets = rockets; } public int getColumnCount() { // Exercice ! } public String getColumnName(int i) { // Exercice ! } public int getRowCount() { // Exercice ! } public Object getValueAt(int row, int col) { // Exercice ! } }

32

Partie I

Patterns dinterface

Exercice 3.5 Compltez le code des mthodes de RocketTableModel qui adaptent un tableau dobjets Rocket pour quil serve dinterface TableModel. Pour obtenir le rsultat de la Figure 3.10, vous pouvez crer deux objets fuse, les placer dans un tableau, crer une instance de RocketTableModel partir du tableau, et utiliser des classes Swing pour afcher ce dernier. La classe ShowRocketTable en donne un exemple :
package app.adapter; import java.awt.Component; import java.awt.Font; import javax.swing.*; import com.oozinoz.firework.Rocket; import com.oozinoz.utility.Dollars; public class ShowRocketTable { public static void main(String[] args) { setFonts(); JTable table = new JTable(getRocketTable()); table.setRowHeight(36); JScrollPane pane = new JScrollPane(table); pane.setPreferredSize( new java.awt.Dimension(300, 100)); display(pane, " Rockets"); } public static void display(Component c, String title) { JFrame frame = new JFrame(title); frame.getContentPane().add(c); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } private static RocketTableModel getRocketTable() { Rocket r1 = new Rocket( "Shooter", 1.0, new Dollars(3.95), 50.0, 4.5); Rocket r2 = new Rocket( "Orbit", 2.0, new Dollars(29.03), 5000, 3.2); return new RocketTableModel(new Rocket[] { r1, r2 }); }

Chapitre 3

ADAPTER

33

private static void setFonts() { Font font = new Font("Dialog", Font.PLAIN, 18); UIManager.put("Table.font", font); UIManager.put("TableHeader.font", font); } }

La classe ShowRocketTable, constitue elle-mme de moins de vingt instructions, gure au-dessus de milliers dautres instructions qui collaborent pour produire un composant table au sein dun environnement GUI (Graphical User Interface). La classe JTable peut grer pratiquement tous les aspects de lafchage dune table mais ne peut savoir lavance quelles donnes vous voudrez prsenter. Pour vous permettre de fournir les donnes dont elle a besoin, elle vous donne la possibilit dappliquer le pattern ADAPTER. Pour utiliser JTable, vous implmentez linterface TableModel quelle attend, ainsi quune classe fournissant les donnes afcher.

Identication dadaptateurs
Le Chapitre 2 a voqu lintrt que prsente la classe WindowAdapter. La classe MouseAdapter illustre Figure 3.11 est un autre exemple de classe stub (cest-dire qui ne dnit pas les mthodes requises par linterface quelle implmente).
Figure 3.11
La classe MouseAdapter implmente la classe MouseListener en laissant vide le corps de ses mthodes.

<<interface>> MouseListener mouseClicked() mouseEntered() mouseExited() mousePressed() mouseReleased() MouseAdapter

mouseClicked() mouseEntered() mouseExited() mousePressed() mouseReleased()

Exercice 3.6 Pouvez-vous considrer que vous appliquez le pattern ADAPTER lorsque vous utilisez la classe MouseAdapter? Expliquez votre rponse.

34

Partie I

Patterns dinterface

Rsum
Le pattern ADAPTER vous permet dutiliser une classe existante pour rpondre aux exigences dune classe cliente. Lorsquun client spcie ses exigences dans une interface, vous pouvez gnralement crer une nouvelle classe qui implmente linterface et tend la classe existante. Cette approche produit un adaptateur de classe qui traduit les appels du client en appels des mthodes de la classe existante. Lorsque le client ne spcie pas linterface dont il a besoin, vous pouvez quand mme appliquer ADAPTER en crant une sous-classe cliente qui utilise une instance de la classe existante. Cette approche produit un adaptateur dobjet qui transmet les appels du client cette instance. Elle nest pas dnue de risques, surtout si vous omettez (ou tes dans limpossibilit) de rednir toutes les mthodes que le client pourrait appeler. Le composant JTable dans Swing est un bon exemple de classe laquelle ses concepteurs ont appliqu le pattern ADAPTER. Il se prsente en tant que client ayant besoin des informations de table telles que dnies par linterface TableModel. Il vous est ainsi plus facile dcrire un adaptateur qui alimente la table en donnes partir dobjets du domaine, tels que des instances de la classe Rocket. Pour utiliser JTable, on cre souvent un adaptateur dobjet qui dlgue les appels aux instances dune classe existante. Deux aspects de JTable font quil est peu probable quun adaptateur de classe soit utilis. Premirement, ladaptateur est habituellement cr en tendant AbstractTableModel, auquel cas il nest pas possible dtendre galement la classe existante. Deuximement, la classe JTable requiert un ensemble dobjets, et un adaptateur dobjet convient mieux pour adapter des informations tires de plusieurs objets. Lorsque vous concevez vos systmes, considrez la puissance et la souplesse offertes par une architecture qui tire parti de ADAPTER.

4
FACADE
Un gros avantage de la POO est quelle permet dviter le dveloppement de programmes monolithiques au code irrmdiablement enchevtr. Dans un systme OO, une application est, idalement, une classe minimale qui unit les comportements dautres classes groupes en kits doutils rutilisables. Un dveloppeur de kits doutils ou de sous-systmes cre souvent des packages de classes bien conues sans fournir dapplications les liant. Les packages dans les bibliothques de classes Java se prsentent gnralement ainsi. Ce sont des kits doutils partir desquels vous pouvez tisser une varit innie dapplications spciques. La rutilisabilit des kits doutils saccompagne dun inconvnient : lapplicabilit diverse des classes dans un sous-systme OO met la disposition du dveloppeur une quantit tellement impressionnante doptions quil lui est parfois difcile de savoir par o commencer. Un environnement de dveloppement intgr, ou IDE (Integrated Development Environment), tel quEclipse, peut affranchir le dveloppeur dune certaine part de la complexit du kit, mais il ajoute en revanche une grande quantit de code que le dveloppeur ne souhaitera pas forcment maintenir. Une autre approche pour simplier lemploi dun kit doutils est de fournir une faade une petite quantit de code qui permet un usage typique peu de frais des classes de la bibliothque. Une faade est elle-mme une classe avec un niveau de fonctionnalits situ entre le kit doutils et une application complte, proposant un emploi simpli des classes dun package ou dun sous-systme. Lobjectif du pattern FACADE est de fournir une interface simpliant lemploi dun sous-systme.

36

Partie I

Patterns dinterface

Faades, utilitaires et dmos


Une classe de faade peut ne contenir que des mthodes statiques, auquel cas elle est appele un utilitaire dans UML (Guide de lutilisateur UML) [Booch, Rumbaugh, et Jacobsen 1999]. Nous introduirons par la suite une classe UI (User Interface), qui aurait pu recevoir seulement des mthodes statiques, bien que procder ainsi aurait empch par la suite la rednition des mthodes dans les sous-classes. Une dmo est un exemple qui montre comment employer une classe ou un soussystme. A cet gard, la valeur des dmos peut tre vue comme tant gale celle des faades. Exercice 4.1 Indiquez deux diffrences entre une dmo et une faade.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

Le package javax.swing contient JOptionPane, une classe qui permet dafcher facilement une bote de dialogue standard. Par exemple, le code suivant afche et rafche une bote de dialogue jusqu ce que lutilisateur clique sur le bouton Yes, comme illustr Figure 4.1.
package app.facade; import javax.swing.*; import java.awt.Font; public class ShowOptionPane { public static void main(String[] args) { Font font = new Font("Dialog", Font.PLAIN, 18); UIManager.put("Button.font", font); UIManager.put("Label.font", font); int option; do { option = JOptionPane.showConfirmDialog( null, "Had enough?",

Chapitre 4

FACADE

37

"A Stubborn Dialog", JOptionPane.YES_NO_OPTION); } while (option == JOptionPane.NO_OPTION); } }

Figure 4.1
La classe JOptionPane facilite lafchage de botes de dialogue.

Exercice 4.2 La classe JOptionPane facilite lafchage dune bote de dialogue. Indiquez si cette classe est une faade, un utilitaire ou une dmo. Justiez votre rponse. Exercice 4.3 Peu de faades apparaissent dans les bibliothques de classes Java. Pour quelle raison ?

Refactorisation pour appliquer FACADE


Les faades sont souvent introduites hors de la phase de dveloppement normal dune application. Lors de la tche de sparation des problmes dans votre code en diverses classes, vous pouvez refactoriser, ou restructurer, le systme en extrayant une classe dont la tche principale est de fournir un accs simpli un soussystme. Considrez un exemple remontant aux premiers jours dOozinoz, o aucun standard de dveloppement de GUI navait encore t adopt. Supposez que vous vous retrouviez examiner une application quun dveloppeur a cre pour afcher la trajectoire dune bombe arienne nayant pas explos. La Figure 4.2 illustre cette classe. Les bombes sont prvues pour exploser trs haut dans le ciel en produisant des effets spectaculaires. Parfois, une bombe nexplose pas du tout. Dans ce cas, son retour sur terre devient intressant. A la diffrence dune fuse, une bombe nest pas auto-propulse. Aussi, si vous ignorez les effets dus au vent et la rsistance de lair, la trajectoire dune bombe ayant un rat est une simple parabole.

38

Partie I

Patterns dinterface

Figure 4.2
La classe ShowFlight afche la trajectoire dune bombe arienne ayant un rat.
JPanel

ShowFlight

ShowFlight() main() createTitledBorder(:String) createTitledPanel(title:String,p:JPanel):JPanel getStandardFont():Font paintComponent(:Graphics)

La Figure 4.3 illustre une capture dcran de la fentre qui apparat lorsque vous excutez ShowFlight.main().
Figure 4.3
Lapplication ShowFlight montre lendroit o une bombe qui na pas explos retombe.

La classe ShowFlight prsente un problme : elle mle trois objectifs. Son objectif principal est dagir en tant que panneau dafchage dune trajectoire. Un deuxime objectif de cette classe est dagir en tant quapplication complte, incorporant et afchant le panneau de trajectoire dans un cadre compos dun titre. Enn, son dernier objectif est de calculer la trajectoire parabolique que suit la bombe dfaillante, le calcul tant ralis dans paintComponent():
protected void paintComponent(Graphics g) { super.paintComponent(g); // dessine larrire-plan

Chapitre 4

FACADE

39

int nPoint = 101; double w = getWidth() - 1; double h = getHeight() - 1; int[] x = new int[nPoint]; int[] y = new int[nPoint]; for (int i = 0; i < nPoint; i++) { // t va de 0 1 double t = ((double) i) / (nPoint - 1); // x va de 0 w x[i] = (int) (t * w); // y est h pour t = 0 et t = 1, et 0 pour t = 0,5 y[i] = (int) (4 * h * (t - .5) * (t - .5)); } g.drawPolyline(x, y, nPoint); }

Voyez lencadr intitul "Equations paramtriques" plus loin dans ce chapitre pour une explication de la faon dont le code dnit les valeurs x et y de la trajectoire. Il nest pas ncessaire davoir un constructeur. Il existe des mthodes statiques utilitaires qui permettent dincorporer un titre dans un cadre et de dnir une police standard.
public static TitledBorder createTitledBorder(String title){ TitledBorder tb = BorderFactory.createTitledBorder( BorderFactory.createBevelBorder(BevelBorder.RAISED), title, TitledBorder.LEFT, TitledBorder.TOP); tb.setTitleColor(Color.black); tb.setTitleFont(getStandardFont()); return tb; } public static JPanel createTitledPanel( String title, JPanel in) { JPanel out = new JPanel(); out.add(in); out.setBorder(createTitledBorder(title)); return out; } public static Font getStandardFont() { return new Font("Dialog", Font.PLAIN, 18); }

Notez que la mthode createTitledPanel() place le composant reu lintrieur dune bordure en relief pour produire un lger espace de remplissage, empchant la courbe de la trajectoire de toucher les bords du panneau. La mthode main() ajoute

40

Partie I

Patterns dinterface

aussi lobjet de formulaire un espace de remplissage quil utilise pour contenir les composants de lapplication :
public static void main(String[] args) { ShowFlight flight = new ShowFlight(); flight.setPreferredSize(new Dimension(300, 200)); JPanel panel = createTitledPanel("Flight Path", flight); JFrame frame = new JFrame("Flight Path for Shell Duds"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(panel); frame.pack(); frame.setVisible(true); }

Lexcution de ce programme produit la fentre illustre Figure 4.3.


Equations paramtriques Lorsque vous devez dessiner une courbe, il peut tre difcile de dcrire des valeurs y en tant que fonctions de valeurs x. Les quations paramtriques permettent de dnir ces deux types de valeurs en fonction dun troisime paramtre. Plus spciquement, vous pouvez dnir un temps t allant de 0 1 alors que la courbe est dessine, et dnir x et y en tant que fonctions du paramtre t. Par exemple, supposez que le trac de la trajectoire parabolique doive stendre sur la largeur w dun objet Graphics. Une quation paramtrique pour x est simple :
x=w*t

Notez que pendant que t passe de 0 1, x va de 0 w. Les valeurs y dune parabole doivent varier avec le carr de la valeur de t, et les valeurs de y doivent augmenter en allant vers le bas de lcran. Pour une trajectoire parabolique, la valeur y devrait tre gale 0 au temps t=0,5. Aussi pouvons-nous crire lquation initiale comme suit :
y=k*(t0,5)*(t0,5)

Ici, k reprsente une constante que nous devons encore dterminer. Lquation prvoit y 0 lorsque t=0,5, et avec une valeur identique pour t=0 et t=1. A ces deux instants t, y devrait tre gale h, la hauteur de la zone dafchage. Avec un peu de manipulation algbrique, vous pouvez trouver lquation complte pour y:
y=4*h*(t0,5)*(t0,5)

La Figure 4.3 illustre le rsultat des quations en action. Un autre avantage des quations paramtriques est quelles ne posent pas de problme pour dessiner des courbes qui possdent plus dune valeur y pour une valeur x. Considrez le dessin dun cercle. Lquation dun cercle avec un rayon de 1 est pose comme suit :
x2+y2=r2

Chapitre 4

FACADE

41

ou :
y=+sqrt(r 2x2)

Devoir grer le fait que deux valeurs y sont produites pour chaque valeur x est compliqu. Il est aussi difcile dajuster ces valeurs pour dessiner correctement la courbe lintrieur des dimensions h (hauteur) et w (largeur) dun objet Graphics. Les coordonnes polaires simplient la fonction pour un cercle :
x=r*cos(theta) y=r*sin(theta)

Ces formules sont des quations paramtriques qui dnissent x et y en tant que fonctions dun nouveau paramtre theta. La variable theta reprsente la courbure dun arc qui varie de 0 2*pi alors que le cercle est dessin. Vous pouvez dnir le rayon dun cercle de manire quil sinscrive lintrieur des dimensions dun objet Graphics. Quelques quations paramtriques sufsent pour dessiner un cercle dans les limites dun tel objet, comme le montre lexemple suivant :
theta=2*pi*t r=min(w,h)/2 x=w/2+r*cos(theta) y=h/2-r*sin(theta)

La transposition de ces quations dans le code produit le cercle illustr Figure 4.4 le code qui produit cet afchage se trouve dans lapplication ShowCircle sur le site oozinoz.com. Le code dessinant un cercle est une transposition relativement directe des formules mathmatiques. Il y a toutefois une subtilit dans ce sens que le code rduit la hauteur et la largeur de lobjet Graphics car les pixels sont numrots de 0 h1 et de 0 w1.
package app.facade; import javax.swing.*; import java.awt.*; import com.oozinoz.ui.SwingFacade; public class ShowCircle extends JPanel { public static void main(String[] args) { ShowCircle sc = new ShowCircle(); sc.setPreferredSize(new Dimension(300, 300)); SwingFacade.launch(sc, "Circle"); } protected void paintComponent(Graphics g) { super.paintComponent(g); int nPoint = 101; double w = getWidth() - 1; double h = getHeight() - 1; double r = Math.min(w, h) / 2.0; int[] x = new int[nPoint]; int[] y = new int[nPoint]; for (int i = 0; i < nPoint; i++) { double t = ((double) i) / (nPoint - 1); double theta = Math.PI * 2.0 * t; x[i] = (int) (w / 2 + r * Math.cos(theta)); y[i] = (int) (h / 2 - r * Math.sin(theta));

42

Partie I

Patterns dinterface

} g.drawPolyline(x, y, nPoint); } }

Exprimer les fonctions x et y par rapport t vous permet de diviser les tches de dtermination des valeurs x et y. Cest souvent plus simple que de devoir dnir y en fonction de x et cela facilite souvent la transposition de x et de y en coordonnes dun objet Graphics. Les quations paramtriques simplient galement le dessin de courbes o y nest pas une fonction monovalue de x. Figure 4.4
Les quations paramtriques simplient la modlisation de courbes lorsque y nest pas une fonction monovalue de x.

Le code de la classe ShowFlight fonctionne, mais vous pouvez le rendre plus facile maintenir et plus rutilisable en le retravaillant pour crer des classes se concentrant sur des problmes distincts. Supposez quaprs une rvision du code, vous dcidiez :
m

Dintroduire une classe Function avec une mthode f() qui accepte un type double (une valeur de temps) et retourne un double (la valeur de la fonction). De dplacer le code dessinant la courbe de la classe ShowFlight vers une classe PlotPanel, mais de le modier pour quil utilise des objets Function pour les valeurs x et y. Dnissez le constructeur PlotPanel de manire quil accepte deux instances de Function ainsi que le nombre de points dessiner. De dplacer la mthode createTitledPanel() vers la classe utilitaire UI pour construire un panneau avec un titre, comme le fait dj la classe ShowFlight.

Chapitre 4

FACADE

43

Exercice 4.4 Compltez le diagramme de la Figure 4.5 pour prsenter le code de ShowFlight rparti en trois types : une classe Function, une classe PlotPanel qui dessine deux fonctions paramtriques, et une classe de faade UI. Dans votre nouvelle conception, faites en sorte que ShowFlight2 cre un objet Function pour les valeurs y et incorpore une mthode main() qui lance lapplication.

Figure 4.5
Lapplication de dessin dune trajectoire parabolique restructure en trois classes sacquittant chacune dune tche.
ShowFlight2 JPanel

PlotPanel

UI

Function

Aprs ces changements, la classe Function dnit lapparence des quations paramtriques. Supposez que vous criez un package com.oozinoz.function pour contenir la classe Function et dautres types. Le cur de Function.java pourrait tre :
public abstract double f(double t);

44

Partie I

Patterns dinterface

La classe PlotPanel rsultant de la restructuration du code na quun travail raliser : afcher une paire dquations paramtriques :
package com.oozinoz.ui; import java.awt.Color; import java.awt.Graphics; import javax.swing.JPanel; import com.oozinoz.function.Function; public class PlotPanel extends JPanel { private int points; private int[] xPoints; private int[] yPoints; private Function xFunction; private Function yFunction; public PlotPanel( int nPoint, Function xFunc, Function yFunc) { points = nPoint; xPoints = new int[points]; yPoints = new int[points]; xFunction = xFunc; yFunction = yFunc; setBackground(Color.WHITE); } protected void paintComponent(Graphics graphics) { double w = getWidth() - 1; double h = getHeight() - 1; for (int i = 0; i < points; i++) { double t = ((double) i) / (points - 1); xPoints[i] = (int) (xFunction.f(t) * w); yPoints[i] = (int) (h * (1 - yFunction.f(t))); } graphics.drawPolyline(xPoints, yPoints, points); } }

Notez que la classe PlotPanel fait maintenant partie du package com.oozinoz.ui, o rside aussi la classe UI. Aprs restructuration de la classe ShowFlight, la classe UI inclut aussi les mthodes createTitledPanel() et createTitledBorder(). La classe UI se transforme en faade qui facilite lemploi de composants graphiques Java.

Chapitre 4

FACADE

45

Une application qui utiliserait ces composants pourrait tre une petite classe ayant pour seule tche de les mettre en place et de les afcher. Par exemple, le code de la classe ShowFlight2 se prsente comme suit :
package app.facade; import import import import import import java.awt.Dimension; javax.swing.JFrame; com.oozinoz.function.Function; com.oozinoz.function.T; com.oozinoz.ui.PlotPanel; com.oozinoz.ui.UI;

public class ShowFlight2 { public static void main(String[] args) { PlotPanel p = new PlotPanel( 101, new T(), new ShowFlight2().new YFunction()); p.setPreferredSize(new Dimension(300, 200)); JFrame frame = new JFrame( "Flight Path for Shell Duds"); frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE); frame.getContentPane().add( UI.NORMAL.createTitledPanel("Flight Path", p)); frame.pack(); frame.setVisible(true); } private class YFunction extends Function { public YFunction() { super(new Function[] {}); } public double f(double t) { // y est 0 pour t = 0 et 1 ; y est 1 pour t = 0,5 return 4 * t * (1 - t); } } }

La classe ShowFlight2 fournit la classe YFunction pour la trajectoire. La mthode main() met en place linterface utilisateur et lafche. Lexcution de cette classe produit les mmes rsultats que la classe ShowFlight originale. La diffrence est que vous disposez maintenant dune faade rutilisable qui simplie la cration dune interface utilisateur graphique dans des applications Java.

46

Partie I

Patterns dinterface

Rsum
Dordinaire, vous devriez refactoriser les classes dun sous-systme jusqu ce que chaque classe ait un objectif spcique bien dni. Cette approche permet dobtenir un code plus facile maintenir. Il est toutefois possible quun utilisateur de votre sous-systme puisse prouver des difcults pour trouver par o commencer. Pour pallier cet inconvnient et aider le dveloppeur exploitant votre code, vous pouvez fournir des dmos ou des faades avec votre sous-systme. Une dmo est gnralement autonome, cest une application non rutilisable qui montre une faon dappliquer un sous-systme. Une faade est une classe congurable et rutilisable, avec une interface de plus haut niveau qui simplie lemploi du sous-systme.

5
COMPOSITE
Un COMPOSITE est un groupe dobjets contenant aussi bien des lments individuels que des lments contenant dautres objets. Certains objets contenus reprsentent donc eux-mmes des groupes et dautres sont des objets individuels appels des feuilles (leaf). Lorsque vous modlisez un objet composite, deux concepts efcaces mergent. Une premire ide importante est de concevoir des groupes de manire englober des lments individuels ou dautres groupes une erreur frquente est de dnir des groupes ne contenant que des feuilles. Un autre concept puissant est la dnition de comportements communs aux deux types dobjets, individuels et composites. Vous pouvez unir ces deux ides en dnissant un type commun aux groupes et aux feuilles, et en modlisant des groupes de faon quils contiennent un ensemble dobjets de ce type. Lobjectif du pattern COMPOSITE est de permettre aux clients de traiter de faon uniforme des objets individuels et des compositions dobjets.

Un composite ordinaire
La Figure 5.1 illustre une structure composite ordinaire. Les classes Leaf et Composite partagent une interface commune, Component. Un objet Composite sous-tend dautres objets Composite et Leaf. Notez que, dans la Figure 5.1, Component est une classe abstraite sans oprations concrtes. Vous pouvez donc la dnir en tant quinterface implmente par Leaf et Composite.

48

Partie I

Patterns dinterface

Figure 5.1
Les concepts essentiels vhiculs par le pattern COMPOSITE sont quun objet composite peut aussi contenir, outre des feuilles, dautres objets composites, et que les nuds composites et feuilles partagent une interface commune.
Component

operation()

Leaf

Composite

operation()

operation() other()

Exercice 5.1 Pourquoi la classe Composite dans la Figure 5.1 sous-tend-elle un ensemble dobjets Component et pas simplement un ensemble de feuilles ?

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

Comportement rcursif dans les objets composites


Les ingnieurs dOozinoz ont peru une composition naturelle dans les machines quils utilisent pour la production de pices dartice. Une unit de production se compose de traves, chaque trave contient une ou plusieurs lignes de montage, et chaque ligne comprend un ensemble de machines qui collaborent pour produire des pices et respecter un calendrier. Les dveloppeurs ont modlis ce domaine en traitant units de production, traves et lignes de montage comme des "machines" composites, en utilisant le diagramme de classes prsent la Figure 5.2. Comme le montre la gure, un comportement qui sapplique la fois aux machines individuelles et aux groupes de machines est getMachineCount(), qui retourne le nombre de machines pour un composant donn.

Chapitre 5

COMPOSITE

49

Exercice 5.2 Ecrivez le code des mthodes getMachineCount() implmentes respectivement par Machine et MachineComposite.
Figure 5.2
La mthode getMachineComponent

MachineCount() est un comportement appropri aussi bien pour les machines individuelles que pour les machines composites.
Machine

getMachineCount()

MachineComposite components:List

getMachineCount() getMachineCount()

Supposez que nous envisagions lajout des mthodes suivantes dans MachineComponent:
Mthode Comportement Indique si toutes les machines dun composant se trouvent dans un tat actif Ordonne toutes les machines dun composant darrter leur travail Retourne un ensemble dingnieurs des mthodes responsables des machines dun composant Retourne tous les produits en cours de traitement dans un composantmachine

isCompletelyUp() stopAll() getOwners() getMaterial()

Le fonctionnement de chaque mthode dans MachineComponent est rcursif. Par exemple, le compte de machines dans un objet composite est le total des comptes de machines de ses composants.

50

Partie I

Patterns dinterface

Exercice 5.3 Pour chaque mthode dclare par MachineComponent, donnez une dnition rcursive pour MachineComposite et non rcursive pour Machine.
Mthode Classe Dnition Retourne la somme des comptes pour chaque composant de Component Retourne 1 ?? ?? ?? ?? ?? ?? ?? ??

getMachineCount()

MachineComposite Machine

isCompletelyUp()

MachineComposite Machine

stopAll()

MachineComposite Machine

getOwners()

MachineComposite Machine

getMaterial()

MachineComposite Machine

Objets composites, arbres et cycles


Dans une structure composite, nous pouvons dire quun nud est un arbre sil contient des rfrences dautres nuds. Cette dnition est cependant trop vague. Pour tre plus prcis, nous pouvons appliquer quelques termes de la thorie des graphes la modlisation dobjets. Nous pouvons commencer par dessiner un modle objet sous forme dun graphe un ensemble de nuds et dartes avec des objets en tant que nuds et des rfrences dobjet en tant quartes. Considrez la modlisation dune analyse (assay) dune prparation (batch) chimique. La classe Assay possde un attribut batch de type Batch, et la classe Batch comprend un attribut chemical de type Chemical. Supposez quil y ait un certain objet Assay dont lattribut batch se rfre un objet b de type Batch, et aussi que lattribut chemical de lobjet b se rfre un objet c de type Chemical.

Chapitre 5

COMPOSITE

51

La Figure 5.3 illustre deux options de diagrammes possibles pour ce modle. Pour plus dinformations sur lillustration de modles objet avec UML, voyez lAnnexe D.
Figure 5.3
Deux options de reprsentation possible des mmes informations : lobjet a rfrence lobjet b, et lobjet b rfrence lobjet c.
a:Assay b:Batch c:Chemical

or:

Il y a un chemin, une srie de rfrences dobjets, de a c, car a rfrence b et b rfrence c. Un cycle est un chemin le long duquel un certain nud apparat deux fois. Il y aurait un cycle de rfrences dans ce modle si lobjet Chemical c rfrenait en retour lobjet Assay a. Les modles objet sont des graphes orients car chaque rfrence dobjet possde une direction. La thorie des graphes applique gnralement le terme arbre pour dsigner certains graphes non orients. Un graphe orient peut toutefois tre appel un arbre si :
m m

Il possde un nud racine qui nest pas rfrenc. Chaque autre nud na quun parent, le nud qui le rfrence.

Pourquoi se proccuper de cette notion darbre pour un graphe ? Parce que le pattern COMPOSITE convient particulirement bien aux structures qui suivent cette forme comme nous le verrons, vous pouvez nanmoins faire fonctionner un COMPOSITE avec un graphe orient acyclique ou mme un graphe cyclique, mais cela demande du travail et une attention supplmentaires. Le modle objet illustr la Figure 5.3 est un simple arbre. Lorsque les modles sont de plus grande taille, il peut tre difcile de savoir sil sagit dun arbre. La Figure 5.4 prsente le modle objet dune usine, appel plant, cest--dire un objet MachineComposite. Cette usine comprend une trave compose de trois machines : un mixeur (mixer), une presse (press) et un assembleur (assembler). Le modle montre aussi que la liste de composants-machines de lobjet plant contient une rfrence directe au mixeur.

52

Partie I

Patterns dinterface

plant:MachineComposite

:List

bay:MachineComposite

:List

mixer:Machine

press

assembler

Figure 5.4
Un modle objet formant un graphe qui nest ni cyclique, ni un arbre.

Le graphe dobjets de la Figure 5.4 ne comprend pas de cycle, mais ce nest pas un arbre car deux objets rfrencent le mme objet mixer. Si nous supprimons ou ne tenons pas compte de lobjet plant et de sa liste, lobjet bay est la racine de larbre. Les mthodes qui sappliquent des composites peuvent avoir des dfauts si elles supposent que tous les composites sont des arbres mais que le systme accepte des composites qui nen sont pas. LExercice 5.2 demandait la dnition dune opration getMachineCount(). Limplmentation de cette opration dans la classe Machine, telle que donne dans la solution de lexercice, est correcte :
public int getMachineCount() { return 1; }

La classe MachineComposite implmente aussi correctement getMachineCount(), retournant la somme des comptes de chaque composant dun composite :
public int getMachineCount() { int count = 0; Iterator i = components.iterator(); while (i.hasNext()) { MachineComponent mc = (MachineComponent) i.next(); count += mc.getMachineCount(); } return count; }

Chapitre 5

COMPOSITE

53

Ces mthodes sont correctes tant que les objets MachineComponent sont des arbres. Il peut toutefois arriver quun composite que vous supposiez tre un arbre ne le soit soudain plus. Cela se produirait vraisemblablement si les utilisateurs pouvaient modier la composition. Considrez un exemple susceptible de se produire chez Oozinoz. Les ingnieurs dOozinoz utilisent une application avec GUI pour enregistrer et actualiser la composition du matriel dans lusine. Un jour, ils signalent un dfaut concernant le nombre de machines rapport existant dans lusine. Vous pouvez reproduire leur modle objet avec la mthode plant() de la classe OozinozFactory:
public static MachineComposite plant() { MachineComposite plant = new MachineComposite(100); MachineComposite bay = new MachineComposite(101); Machine mixer = new Mixer(102); Machine press = new StarPress(103); Machine assembler = new ShellAssembler(104); bay.add(mixer); bay.add(press); bay.add(assembler); plant.add(mixer); plant.add(bay); return plant; }

Ce code produit lobjet plant vu plus haut dans la Figure 5.4. Exercice 5.4 Que renvoie en sortie le programme suivant ?
package app.composite; import com.oozinoz.machine.*; public class ShowPlant { public static void main(String[] args) { MachineComponent c = OozinozFactory.plant(); System.out.println( "Nombre de machines : " + c.getMachineCount()); } }

54

Partie I

Patterns dinterface

Lapplication avec GUI utilise chez Oozinoz pour construire les modles objet de lquipement dune usine devrait vrier si un nud existe dj dans un arbre de composant avant de lajouter une seconde fois. Un moyen daccomplir cela est de conserver un ensemble des nuds existants. Il peut toutefois arriver que vous nayez pas le contrle sur la formation dun composite. Dans ce cas, vous pouvez crire une mthode isTree() pour vrier si un composite est un arbre. Nous considrerons un modle objet comme tant un arbre si un algorithme peut parcourir ses rfrences sans traverser deux fois le mme nud. Vous pouvez implmenter une mthode isTree() sur la classe abstraite MachineComponent an de dlguer lappel une mthode isTree() conservant un ensemble des nuds parcourus. La classe MachineComponent peut laisser abstraite limplmentation de la mthode isTree(set:Set) paramtre. La Figure 5.5 illustre le placement des mthodes isTree(). Le code de MachineComponent dlgue un appel isTree() sa mthode abstraite isTree(s:Set):
public boolean isTree() { return isTree(new HashSet()); } protected abstract boolean isTree(Set s);

Ces mthodes emploient la classe Set de la bibliothque de classes Java.


Figure 5.5
Une mthode isTree() peut dtecter si un composite est en ralit un arbre.
MachineComponent

MachineComponent(id:int) id:int isTree():boolean isTree(set:Set):boolean

Machine

MachineComposite

Machine(id:int) isTree(set:Set)

MachineComposite(id:int) isTree(set:Set)

Chapitre 5

COMPOSITE

55

Les classes Machine et MachineComposite doivent implmenter la mthode abstraite isTree(s:Set). Limplmentation de isTree() pour Machine est simple, retant le fait que des machines individuelles sont toujours des arbres :
protected boolean isTree(Set visited) { visited.add(this); return true; }

Limplmentation dans MachineComposite de isTree() doit ajouter lobjet rcepteur la collection visited puis parcourir tous les composants du composite. La mthode peut retourner false si un composant a dj t parcouru ou nest pas un arbre. Sinon, elle retourne true. Exercice 5.5 Ecrivez le code pour MachineComposite.isTree(Set visited).

En procdant avec soin, vous pouvez garantir quun modle objet reste un arbre en refusant tout changement qui ferait retourner false par isTree(). Dun autre ct, vous pouvez dcider dautoriser lexistence de composites qui ne sont pas des arbres, surtout lorsque le domaine de problmes que vous modlisez contient des cycles.

Des composites avec des cycles


Le composite non-arbre auquel se rfrait lExercice 5.4 tait un accident d au fait quun utilisateur avait marqu une machine comme faisant partie la fois dune usine (plant) et dune trave (bay). Pour les objets physiques, vous pouvez prfrer interdire le concept dobjet contenu par plus dun autre objet. Toutefois, un domaine de problmes peut comprendre des lments non physiques pour lesquels des cycles de connement sont justis. Cela se produit frquemment lors de la modlisation de ux de processus. Considrez la construction de bombes ariennes telles que celle illustre Figure 5.6. Une bombe est lance au moyen dun mortier, ou tube, par la mise feu de la charge de propulsion (contenant de la poudre noire) loge sous la charge centrale. Le deuxime dispositif dallumage brle alors que la bombe est en lair pour nalement atteindre la charge centrale lorsque la bombe est son apoge.

56

Partie I

Patterns dinterface

Lorsque celle-ci explose, les toiles mises feu produisent les effets visuels des feux dartice.
Figure 5.6
Une bombe arienne utilise deux charges : lune pour la propulsion initiale et lautre pour faire clater le cur contenant les toiles lorsque la bombe atteint son apoge.

Cur Coque interne Coque externe Etoiles Charge de propulsion Dispositif d'allumage

Le ux des processus de construction dune bombe arienne commence par la fabrication dune coque interne, suivie dune vrication, puis dune amlioration ou de son assemblage nal. Pour fabriquer la coque interne, un oprateur utilise un assembleur de coques qui place les toiles dans un compartiment hmisphrique, insre une charge centrale de poudre noire, ajoute davantage dtoiles au-dessus de la charge, puis ferme le tout au moyen dun autre compartiment hmisphrique. Un inspecteur vrie que la coque interne rpond aux standards de scurit et de qualit. Si ce nest pas le cas, loprateur la dsassemble et recommence. Si elle passe linspection, loprateur ajoute un dispositif dallumage pour joindre une charge de propulsion la coque interne, puis termine en ajoutant une enveloppe. Comme pour les composites de machines, les ingnieurs dOozinoz disposent dune application avec GUI leur permettant de dcrire la composition dun processus. La Figure 5.7 montre la structure des classes qui grent la modlisation du processus. La Figure 5.8 prsente les objets qui reprsentent le ux des processus participant la fabrication dune bombe arienne. Le processus make est une squence compose de ltape buildInner suivie de ltape inspect et du sous-processus reworkOrFinish. Ce sous-processus prend lune des deux voies possibles. Il peut requrir une tape de dsassemblage suivie du processus make, ou seulement dune tape finish.

Chapitre 5

COMPOSITE

57

Figure 5.7
Le processus de construction de pices dartice inclut des tapes qui sont des alternances ou des squences dautres tapes.
ProcessComponent

ProcessComponent(name:String) getStepCount() getStepCount(s:Set) name:String toString()

ProcessStep

ProcessComposite subprocesses:List

getStepCount(s:Set) add(c:ProcessComponent) getStepCount(s:sET)

ProcessAlternation

ProcessSequence

Exercice 5.6 La Figure 5.8 illustre les objets du modle du processus dassemblage dune bombe. Un diagramme objet complet montrerait les relations entre tous les objets se rfrenant. Par exemple, le diagramme montre les rfrences que lobjet make entretient. Votre travail est de complter les relations manquantes dans le diagramme. Lopration getStepCount() dans la hirarchie ProcessComponent compte le nombre dtapes individuelles dans le ux de processus. Notez que ce compte nest pas la longueur du processus mais le nombre dtapes de traitement de nuds feuilles du graphe du processus. La mthode getStepCount() doit prendre soin de compter une fois chaque tape et de ne pas entrer dans une boucle innie lorsquun processus comprend un cycle. La classe ProcessComponent implmente la

58

Partie I

Patterns dinterface

Figure 5.8
Une fois termin, ce diagramme reprsentera un modle objet du processus de fabrication de bombes ariennes Oozinoz.
buildInnerShell: ProcessStep

make: ProcessSequence

inspect: ProcessStep

reworkOrFinish: ProcessAlternation

:ProcessSequence

disassemble: ProcessStep

finish: ProcessStep

mthode getStepCount() de sorte quelle sappuie sur une mthode compagnon qui transmet un ensemble de nuds parcourus :
public int getStepCount() { return getStepCount(new HashSet()); } public abstract int getStepCount(Set visited);

La classe ProcessComposite veille dans son implmentation ce que la mthode getStepCount() ne parcoure pas un nud dj visit :
public int getStepCount(Set visited) { visited.add(getName()); int count = 0; for (int i = 0; i < subprocesses.size(); i++) { ProcessComponent pc = (ProcessComponent) subprocesses.get(i); if (!visited.contains(pc.getName())) count += pc.getStepCount(visited); } return count; }

Chapitre 5

COMPOSITE

59

Limplmentation de getStepCount() de la classe ProcessStep est simple :


public int getStepCount(Set visited) { visited.add(name); return 1; }

Le package com.oozinoz.process dOozinoz contient une classe ShellProcess qui inclut une mthode make() qui retourne lobjet make illustr Figure 5.8. Le package com.oozinoz.testing comprend une classe ProcessTest qui fournit des tests automatiss de divers types de graphes de processus. Par exemple, la classe ProcessTest inclut une mthode qui vrie que lopration getStepCount() compte correctement le nombre dtapes dans le processus make cyclique :
public void testShell() { assertEquals(4, ShellProcess.make().getStepCount()); }

Ce test sexcute au sein du framework JUnit. Voir www.junit.org pour plus dinformations sur JUnit.

Consquences des cycles


Beaucoup doprations sur un composite, telles que le calcul de son nombre de nuds feuilles, sont justies mme si le composite nest pas un arbre. Gnralement, la seule diffrence que les composites non-arbre introduisent est que vous devez tre attentif ne pas oprer une deuxime fois sur un mme nud. Toutefois, certaines oprations deviennent inutiles si le composite contient un cycle. Par exemple, nous ne pouvons pas dterminer par voie algorithmique le nombre maximal dtapes requises pour fabriquer une bombe arienne chez Oozinoz car le nombre de fois o ltape damlioration doit tre recommence ne peut tre connu. Toute opration dpendant de la longueur dun chemin dans un composite ne serait pas logique si le composite comprend un cycle. Aussi, bien que nous puissions parler de la hauteur dun arbre le chemin le plus long de la racine une feuille , il ny a pas de longueur de chemin maximale dans un graphe cyclique. Une autre consquence de permettre lintroduction de composites non-arbre est que vous perdez la capacit de supposer que chaque nud na quun parent. Si un composite nest pas un arbre, un nud peut avoir plus dun parent. Par exemple, le processus modlis dans la Figure 5.8 pourrait avoir plusieurs tapes composites utilisant ltape inspect, donnant ainsi lobjet inspect plusieurs parents.

60

Partie I

Patterns dinterface

Il ny a pas de problme inhrent au fait davoir un nud avec plusieurs parents, mais votre modle et votre code doivent en tenir compte.

Rsum
Le pattern COMPOSITE comprend deux concepts puissants associs. Le premier est quun groupe dobjets peut contenir des lments individuels mais aussi dautres groupes. Lautre ide est que ces lments individuels et composites partagent une interface commune. Ces concepts sont unis dans la modlisation objet, lorsque vous crez une classe abstraite ou une interface Java qui dnit des comportements communs des objets composites et individuels. La modlisation de composites conduit souvent une dnition rcursive des mthodes sur les nuds composites. Lorsquil y a une rcursivit, il y a le risque dcrire du code produisant une boucle innie. Pour viter ce problme, vous pouvez prendre des mesures pour garantir que vos composites soient toujours des arbres. Une autre possibilit est dautoriser lintervention de cycles dans un composite, mais il vous faut modier vos algorithmes pour viter toute rcursivit innie.

6
BRIDGE
Le pattern BRIDGE, ou Driver, vise implmenter une abstraction. Le terme abstraction se rfre une classe qui sappuie sur un ensemble doprations abstraites, lesquelles peuvent avoir plusieurs implmentations. La faon habituelle dimplmenter une abstraction est de crer une hirarchie de classes, avec une classe abstraite au sommet qui dnit les oprations abstraites. Chaque sous-classe de la hirarchie apporte une implmentation diffrente de lensemble doprations. Cette approche devient insufsante lorsquil vous faut driver une sous-classe de la hirarchie pour une quelconque autre raison. Vous pouvez crer un BRIDGE (pont) en dplaant lensemble doprations abstraites vers une interface de sorte quune abstraction dpendra dune implmentation de linterface. Lobjectif du pattern BRIDGE est de dcoupler une abstraction de limplmentation de ses oprations abstraites, permettant ainsi labstraction et son implmentation de varier indpendamment.

Une abstraction ordinaire


Presque chaque classe est une abstraction dans ce sens quelle constitue une approximation, une idalisation, ou une simplication de la catgorie dobjets rels quelle modlise. Toutefois, dans le cas du BRIDGE, nous utilisons spciquement le terme abstraction pour signier une classe sappuyant sur un ensemble doprations abstraites.

62

Partie I

Patterns dinterface

Supposez que vous ayez des classes de contrle qui interagissent avec certaines machines produisant les pices dartice chez Oozinoz. Ces classes retent les diffrences dans la faon dont les machines oprent. Vous pourriez toutefois dsirer crer certaines oprations abstraites qui produiraient les mmes rsultats sur nimporte quelle machine. La Figure 6.1 montre des classes de contrle provenant du package com.oozinoz.controller.
Figure 6.1
Ces deux classes ont des mthodes semblables que vous pouvez placer dans un modle commun pour piloter des machines.
StarPressController FuserController

start() stop() startProcess() endProcess() index() discharge()

startMachine() stopMachine() begin() end() conveyIn() conveyOut() switchSpool()

Les deux classes de la Figure 6.1 possdent des mthodes semblables pour dmarrer et arrter les machines quelles contrlent : presse toiles (star press) ou assembleuse de dispositif dallumage (fuser). Elles sont toutefois nommes diffremment : start() et stop() dans la classe StarPressController, et startMachine() et stopMachine() dans FuserController. Ces classes de contrle, ou contrleurs, possdent galement des mthodes pour amener une caisse dans la zone de traitement (index() et conveyIn()), pour dbuter et terminer le traitement dune caisse (startProcess() et endProcess(), et begin() et end()), et pour retirer une caisse (discharge() et conveyOut()). La classe FuserController possde galement une mthode switchSpool() qui permet de changer la bobine de mche dallumage (fuse spool). Supposez maintenant que vous souhaitiez crer une mthode shutdown() qui assure un arrt en bon ordre, effectuant les mmes tapes sur les deux machines. Pour en simplier lcriture, vous pouvez standardiser les noms des oprations courantes, comme startMachine(), stopMachine(), startProcess(), stopProcess(), conveyIn(), et conveyOut(). Il se trouve toutefois que vous ne pouvez pas changer les classes de contrle car lune delles provient du fournisseur de la machine.

Chapitre 6

BRIDGE

63

Exercice 6.1 Indiquez de quelle manire vous pourriez appliquer un pattern de conception pour permettre le contrle de diverses machines avec une interface commune.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.


La Figure 6.2 illustre lintroduction dune classe abstraite MachineManager avec des sous-classes qui retransmettent les appels de contrle en les adaptant au sein de mthodes supportes par FuserController et StarPressController.

MachineManager

startMachine() stopMachine() startProcess() stopProcess() conveyIn() conveyOut() shutdown() StarPressController FuserController

FuserManager

StarPressManager

Figure 6.2
Les classes FuserManager et StarPressManager implmentent les mthodes abstraites de MachineManager en transmettant les appels aux mthodes correspondantes des objets FuserController et StarPressController.

Il nest pas problmatique quun contrleur incorpore des oprations qui soient uniques pour le type de machine concern. Par exemple, bien que la Figure 6.2 ne le montre pas, la classe FuserManager possde galement une mthode

64

Partie I

Patterns dinterface

switchSpool() qui transmet les appels la mthode switchSpool() dun objet FuserController. Exercice 6.2 Ecrivez une mthode shutdown() qui terminera le traitement pour la classe MachineManager, dchargera la caisse en cours de traitement et arrtera la machine.

La mthode shutdown() de la classe MachineManager nest pas abstraite mais concrte. Toutefois, nous pouvons dire que cest une abstraction car la mthode universalise, ou abstrait, la dnition des tapes raliser pour arrter une machine.

De labstraction au pattern BRIDGE


La hirarchie MachineManager tant code par rapport au type dquipement, chaque type de machine ncessite une sous-classe diffrente de MachineManager. Que se passerait-il si vous deviez organiser la hirarchie selon un autre critre ? Par exemple, supposez que vous travailliez directement sur les machines et que celles-ci fournissent un acquittement des tapes quelles accomplissent. Conformment cela, vous voulez crer une sous-classe MachineManager de mise en route, ou handshaking, avec des mthodes permettant de paramtrer linteraction avec la machine, telle que la dnition dune valeur de temporisation. Vous avez toutefois besoin de diffrents gestionnaires de machine pour les presses toiles et les assembleuses de dispositif dallumage. Si vous ne rorganisiez pas dabord la hirarchie MachineManager, votre nouvelle hirarchie risquerait de ressembler au modle de la Figure 6.3. La hirarchie illustre la Figure 6.3 conoit les classes suivant deux critres : selon le type de machine et selon que la machine gre ou non le protocole de mise en route. Ce principe dual de codage prsente un problme. Plus particulirement, une mthode telle que setTimeout() peut contenir un code identique deux endroits, mais nous ne pouvons pas le coder dans la hirarchie car les super-classes ne grent pas lide du handshaking. En gnral, les classes de handshaking ne disposent daucun moyen pour partager le code car il ny a pas de super-classe de handshaking. Et mesure que nous ajoutons

Chapitre 6

BRIDGE

65

Figure 6.3
Les sous-classes de mise en route (Hsk) ajoutent un paramtrage pour le temps dattente dun acquittement de la part dune machine.
MachineManager

FuserManager

StarPressManager

HskFuserManager

HskStarPressManager

setTimeout(:double)

setTimeout(:double)

davantage de classes dans la hirarchie, le problme empire. Si nous disposons au nal de contrleurs pour cinq machines et que la mthode setTimeout() doive tre change, nous devons modier le code cinq endroits. Dans une telle situation, nous pouvons appliquer le pattern BRIDGE. Nous pouvons dissocier labstraction MachineManager de limplmentation de ses oprations abstraites en plaant les mthodes abstraites dans une hirarchie distincte. La classe MachineManager demeure une abstraction et le rsultat produit par lappel de ses mthodes sera diffrent selon quil sagira dune presse ou dune assembleuse. Sparer labstraction de limplmentation de ses mthodes permet aux deux hi rarchies de varier de manire indpendante. Nous pouvons ajouter un support pour de nouvelles machines sans inuer sur la hirarchie MachineManager. Nous pouvons galement tendre la hirarchie MachineManager sans changer aucun des contrleurs de machine. La Figure 6.4 prsente la sparation souhaite. Lobjectif de la nouvelle conception est de sparer la hirarchie MachineManager de limplmentation des oprations abstraites de la hirarchie. Exercice 6.3 La Figure 6.4 illustre la hirarchie MachineManager restructure en BRIDGE. Ajoutez les mentions manquantes.

66

Partie I

Patterns dinterface

Notez que dans la Figure 6.4 la classe MachineManager2 devient concrte bien quelle soit toujours une abstraction. Les mthodes abstraites dont dpend maintenant MachineManager rsident dans linterface MachineDriver. Le nom de cette interface suggre que les classes qui adaptent les requtes de MachineManager aux diffrentes machines spciques sont devenues des drivers. Un driver est un objet qui pilote un systme informatique ou un quipement externe selon une interface bien spcie. Les drivers fournissent lexemple le plus courant dapplication du pattern BRIDGE.
interface MachineManager2 driver:?? ?? shutdown() ?? ?? ?? ?? ?? ?? MachineDriver

??

??

StarPressDriver

Figure 6.4
Une fois complt, ce diagramme montrera la sparation de labstraction MachineManager de limplmentation de ses oprations abstraites.

Des drivers en tant que BRIDGE


Les drivers sont des abstractions. Le rsultat de lexcution de lapplication dpend du driver en place. Chaque driver est une instance du pattern ADAPTER, fournissant linterface quun client attend en utilisant les services dune classe comportant une interface diffrente. Une conception globale qui utilise des drivers est une instance

Chapitre 6

BRIDGE

67

de BRIDGE. La conception spare le dveloppement dapplication de celui des drivers qui implmentent les oprations abstraites dont dpendent les applications. Une conception base de drivers vous force crer un modle abstrait commun de la machine ou du systme piloter. Cela prsente lavantage de permettre au code du ct abstraction de sappliquer nimporte lequel des drivers au travers desquels il pourrait sexcuter. La dnition dun ensemble commun de mthodes pour les drivers peut toutefois prsenter linconvnient dliminer le comportement quun quipement pilot pourrait supporter. Rappelez-vous de la Figure 6.1 quun contrleur dassembleuse de dispositif dallumage possde une mthode switchSpool(). O cette mthode est-elle passe dans la conception rvise de la Figure 6.4 (ou Figure B5 de lAnnexe B) ? La rponse est que nous lavons limine par abstraction. Vous pouvez linclure dans la nouvelle classe FuserDriver. Toutefois, ceci peut donner lieu du code ct abstraction devant procder une vrication pour savoir si son driver est une instance de FuserDriver. Pour viter de perdre la mthode switchSpool(), nous pourrions faire en sorte que chaque driver limplmente, sachant que certains dentre eux ignoreront simplement lappel. Lorsque vous devez choisir un modle abstrait des oprations quun driver doit grer, vous tes souvent confront ce genre de dcision. Vous pouvez inclure des mthodes que certains drivers ne supporteront pas, ou exclure des mthodes pour limiter ce que les abstractions pourront faire avec un driver ou bien les forcer inclure du code pour un cas particulier.

Drivers de base de donnes


Un exemple banal dapplication utilisant des drivers est laccs une base de donnes. La connectivit base de donnes dans Java sappuie sur JDBC. Une bonne source de documentation expliquant comment appliquer JDBC est JDBC API Tutorial and Reference (2/e) [White et al. 1999]. Dit succinctement, JDBC est une API (Application Programming Interface) qui permet dexcuter des instructions SQL (Structured Query Langage). Les classes qui implmentent linterface sont des drivers JDBC, et les applications qui sappuient sur ces drivers sont des abstractions qui peuvent fonctionner avec nimporte quelle base de donnes pour laquelle il existe un driver JDBC. Larchitecture JDBC dissocie une abstraction de son implmentation pour que les deux puissent varier de manire indpendante ; cest un excellent exemple de BRIDGE.

68

Partie I

Patterns dinterface

Pour utiliser un driver JDBC, vous le chargez, le connectez la base de donnes et crez un objet Statement:
Class.forName(driverName); Connection c = DriverManager.getConnection(url, user, pwd); Statement stmt = c.createStatement();

Une description du fonctionnement de la classe DriverManager sortirait du cadre de la prsente description. Sachez toutefois qu ce stade stmt est un objet Statement capable dmettre des requtes SQL qui retournent des ensembles de rsultats (result set) :
ResultSet result = stmt.executeQuery( "SELECT name, apogee FROM firework"); while (result.next()) { String name = result.getString("name"); int apogee = result.getInt("apogee"); System.out.println(name + ", " + apogee); }

Exercice 6.4 La Figure 6.5 illustre un diagramme de squence UML qui dcrit le ux de messages dans une application JDBC typique. Compltez les noms de types et le nom de message manquants.

Figure 6.5
Ce diagramme montre une partie du ux de messages typique qui intervient dans une application JDBC.

:Client createStatement()

:?

<<create>> :? ??() <<create>> :? next()

Chapitre 6

BRIDGE

69

Exercice 6.5 Supposez que chez Oozinoz nous nayons que des bases de donnes SQL Server. Donnez un argument en faveur de lemploi de lecteurs et dadaptateurs spciques SQL Server. Donnez un autre argument qui justierait de ne pas le faire. Larchitecture JDBC divise clairement les rles du dveloppeur de driver et du dveloppeur dapplication. Dans certains cas, cette division nexistera pas lavance, mme si vous utilisez des drivers. Vous pourrez ventuellement implmenter des drivers en tant que sous-classes dune super-classe abstraite, avec chaque sous-classe pilotant un sous-systme diffrent. Dans une telle situation, vous pouvez envisager limplmentation dun BRIDGE lorsquil vous faut davantage de souplesse.

Rsum
Une abstraction est une classe qui dpend de mthodes abstraites. Lexemple le plus simple dabstraction est une hirarchie abstraite, o des mthodes concrtes dans la super-classe dpendent dautres mthodes abstraites. Vous pouvez tre forc de dplacer ces dernires vers une autre hirarchie si vous voulez restructurer la hirarchie originale selon un autre critre. Il sagit alors dune application du pattern BRIDGE, sparant une abstraction de limplmentation de ses mthodes abstraites. Lexemple le plus courant dapplication de BRIDGE apparat dans les drivers, tels que ceux de base de donnes. Les drivers de base de donnes sont un bon exemple des compromis inhrents une conception avec BRIDGE. Un driver peut ncessiter des mthodes quun implmenteur ne peut grer. Dun autre ct, un driver peut ngliger des mthodes utiles qui pourraient sappliquer une certaine base de donnes. Cela pourrait vous inciter rcrire du code spcique une implmentation au lieu dtre abstrait. Il nest pas toujours vident de savoir sil faut privilgier labstraction ou la spcicit, mais il est important de prendre des dcisions mrement rchies.

II
Patterns de responsabilit

7
Introduction la responsabilit
La responsabilit dun objet est comparable celle dun reprsentant au centre de rception des appels de la socit Oozinoz. Lorsquun client appelle chez Oozinoz, la personne qui rpond est un intermdiaire, ou proxy pour reprendre un terme informatique, qui reprsente la socit. Ce reprsentant effectue des tches prvisibles, gnralement en les dlguant un autre systme ou une autre personne. Parfois, le reprsentant dlgue une requte une seule autorit centrale qui joue le rle de mdiateur dans une situation ou transmet les problmes le long dune chane de responsabilits. A linstar des reprsentants, les objets ordinaires disposent des informations et des mthodes adquates pour pouvoir oprer de manire indpendante. Cependant, il y a des situations qui demandent de scarter de ce modle de fonctionnement indpendant et de recourir une entit centrale responsable. Il existe plusieurs patterns qui rpondent ce type de besoin. Il y a aussi des patterns qui permettent aux objets de relayer les requtes et qui isolent un objet des autres objets qui en dpendent. Les patterns affrents la responsabilit fournissent des techniques pour centraliser, transmettre et aussi limiter la responsabilit des objets.

Responsabilit ordinaire
Bien que vous ayez probablement une bonne ide de la faon dont les attributs et les responsabilits doivent tre associs dans une classe bien conue, il pourrait vous paratre difcile dexpliquer les raisons motivant vos choix.

74

Partie II

Patterns de responsabilit

Exercice 7.1 La structure de la classe illustre Figure 7.1 prsente au moins dix choix dassignation de responsabilits discutables. Identiez tous les problmes possibles et expliquez par crit ce qui est erron pour quatre dentre eux.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.


Figure 7.1
Quest-ce qui ne va pas dans cette gure ?
Rocket

interface Runnable

thrust():Rocket

run()

LiquidRocket

CheapRockets

isLiquid() getLocation()

run()

Firework rez:Reservation price:Dollars getPrice() getReservation()

Reservation loc:Location city:String getCity() getDate()

Location

Lexamen des bizarreries de la Figure 7.1 dbridera votre rexion pour entreprendre une modlisation dobjets approprie. Cest ltat desprit qui convient lorsque vous dnissez des termes tels que classe. La valeur de lexercice de dnition de

Chapitre 7

Introduction la responsabilit

75

termes augmente sil favorise la communication entre les personnes et diminue sil devient un objectif en soi et une source de conits. Dans ce mme tat desprit, rpondez la difcile question de lExercice 7.2. Exercice 7.2 Dnissez les qualits dune classe efcace et utile.

Lemploi dune classe est facilit si ses mthodes ont un nom sufsamment explicite pour quon puisse savoir ce quelles ralisent. Il y a toutefois des cas o un nom de mthode ne contient pas sufsamment dinformations pour quon puisse prdire leffet quaura un appel de la mthode. Exercice 7.3 Donnez un exemple de raison pour laquelle limpossibilit de prdire leffet produit par un appel de mthode serait justie.

Llaboration de principes relatifs lassignation de responsabilits dans un systme orient objet est un domaine qui ncessite des progrs. Un systme dans lequel chaque classe et mthode dnit clairement ses responsabilits et sen acquitte correctement est un systme puissant, suprieur la plupart des systmes que lon rencontre aujourdhui.

Contrle de la responsabilit grce la visibilit


Il est courant de parler de classes et de mthodes assumant diverses responsabilits. Dans la pratique, cela signie gnralement que vous, en tant que dveloppeur, assumez la responsabilit dune conception robuste et dun fonctionnement correct du code. Heureusement que Java apporte quelque soulagement dans ce domaine. Vous pouvez limiter la visibilit de vos classes, champs et mthodes, et circonscrire ainsi la responsabilit au niveau des dveloppeurs qui emploient votre code. La visibilit peut tre un signe de la faon dont une portion dune classe doit tre expose.

76

Partie II

Patterns de responsabilit

Le Tableau 7.1 reprend les dnitions informelles de leffet des modicateurs daccs.
Tableau 7.1 : Dnitions informelles de leffet des modicateurs daccs

Accs

Dnition informelle Accs non limit Accs limit au package Accs limit la classe contenant llment en question, ou aux types drivs de cette classe Accs limit au type contenant llment en question

public
(rien)

protected private

Dans la pratique, certaines subtilits demandent de considrer plutt la dnition formelle de ces modicateurs daccs quune dnition intuitive. Par exemple, la question de savoir si une visibilit affecte des objets ou des classes. Exercice 7.4 Un objet peut-il se rfrer un membre priv dune autre instance de la mme classe ? Plus spciquement, le code suivant compilera-t-il ?
public class Firework { private double weight = 0; /// ... private double compare(Firework f) { return weight - f.weight; } }

Les modicateurs daccs vous aident limiter votre responsabilit en restreignant les services que vous fournissez aux autres dveloppeurs. Par exemple, si vous ne voulez pas que dautres dveloppeurs puissent manipuler un champ de lune de vos classes, vous pouvez le dnir private. Inversement, vous apporterez de la souplesse pour les autres dveloppeurs en dclarant un lment protected, bien quil y ait le risque dassocier trop troitement les sous-classes la classe parent. Prenez des dcisions mrement rchies et, au besoin, mettez en place une stratgie de groupe concernant la faon dont vous voulez restreindre les accs pour limiter vos responsabilits tout en autorisant des extensions futures.

Chapitre 7

Introduction la responsabilit

77

Rsum
En tant que dveloppeur Java, vous tes responsable de la cration des classes formant un ensemble logique dattributs et de comportements associs. Crer une classe efcace est un art, mais il est possible dtablir certaines caractristiques dune classe bien conue. Une de vos responsabilits est aussi de vous assurer que les mthodes de vos classes assurent bien les services que leur nom suggre. Vous pouvez limiter cette responsabilit avec lemploi appropri de modicateurs de visibilit, mais envisagez lexistence de certains compromis entre scurit et souplesse au niveau de la visibilit de votre code.

Au-del de la responsabilit ordinaire


Indpendamment de la faon dont une classe restreint laccs ses membres, le dveloppement OO rpartit normalement les responsabilits entre objets individuels. En dautres termes, le dveloppement OO promeut lencapsulation, lide quun objet travaille sur ses propres donnes. La responsabilit distribue est la norme, mais plusieurs patterns de conception sy opposent et placent la responsabilit au niveau dun objet intermdiaire ou dun objet central. Par exemple, le pattern SINGLETON concentre la responsabilit au niveau dun seul objet et fournit un accs global cet objet. Une faon de se souvenir de lobjectif de ce pattern, ainsi que de celui dautres patterns, est de les voir en tant quexceptions la rgle ordinaire de la responsabilit rpartie.
Si vous envisagez de Centraliser la responsabilit au niveau dune instance de classe Librer un objet de la "conscience" de connatre les objets qui en dpendent Centraliser la responsabilit au niveau dune classe qui supervise la faon dont les objets interagissent Laisser un objet agir au nom dun autre objet Autoriser une requte tre transmise le long dune chane dobjets jusqu celui qui la traitera Centraliser la responsabilit au niveau dobjets partags de forte granularit Appliquez le pattern

SINGLETON OBSERVER MEDIATOR PROXY CHAIN OF RESPONSABILITY FLYWEIGHT

78

Partie II

Patterns de responsabilit

Lobjectif de chaque pattern de conception est de permettre la rsolution dun problme dans un certain contexte. Les patterns de responsabilit conviennent dans des contextes o vous devez vous carter de la rgle normale stipulant que la responsabilit devrait tre distribue autant que possible.

8
SINGLETON
Les objets peuvent gnralement agir de faon responsable en effectuant leur travail sur leurs propres attributs, sans avoir dautre obligation que dassurer leur cohrence propre. Cependant, certains objets assument dautres responsabilits, telles que la modlisation dentits du monde rel, la coordination de tches, ou la modlisation de ltat gnral dun systme. Lorsque, dans un systme, un certain objet assume une responsabilit dont dpendent dautres objets, vous devez disposer dune mthode pour localiser cet objet. Par exemple, vous pouvez avoir besoin didentier un objet qui reprsente une machine particulire, un objet client qui puisse se construire lui-mme partir dinformations extraites dune base de donnes, ou encore un objet qui initie une rcupration de la mmoire systme. Dans certains cas, lorsque vous devez trouver un objet responsable, lobjet dont vous avez besoin sera la seule instance de sa classe. Par exemple, la cration de fuses peut se sufre dun seul objet Factory. Dans ce cas, vous pouvez utiliser le pattern SINGLETON. Lobjectif du pattern SINGLETON est de garantir quune classe ne possde quune seule instance et de fournir un point daccs global celle-ci.

Le mcanisme de SINGLETON
Le mcanisme de SINGLETON est plus simple exposer que son objectif. En effet, il est plus ais dexpliquer comment garantir quune classe naura quune instance que pourquoi cette restriction est souhaitable. Vous pouvez placer SINGLETON dans la catgorie "patterns de cration", comme le fait louvrage Design Patterns.

80

Partie II

Patterns dinterface

Lessentiel est de voir les patterns dune faon qui permette de sen souvenir, de les reconnatre et de les appliquer. Lintention, ou lobjectif, du pattern SINGLETON demande quun objet spcique assume une responsabilit dont dpendent dautres objets. Vous disposez de quelques options quant la faon de crer un objet qui remplit un rle de manire unique. Indpendamment de la faon dont vous crez un singleton, vous devez vous assurer que dautres dveloppeurs ne crent pas de nouvelles instances de la classe que vous souhaitez restreindre. Exercice 8.1 Comment pouvez-vous empcher dautres dveloppeurs de crer de nouvelles instances de votre classe ?

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

Lorsque vous concevez une classe singleton, vous devez dcider du moment de linstanciation du seul objet qui reprsentera la classe. Une possibilit est de crer cette instance en tant que champ statique dans la classe. Par exemple, une classe SystemStartup pourrait inclure la ligne :
private static Factory factory = new Factory();

Cette classe pourrait rendre son unique instance disponible par lintermdiaire dune mthode getFactory() publique et statique. Plutt que de crer lavance une instance de singleton, vous pouvez attendre jusquau moment o linstance est requise pour la premire fois en utilisant une initialisation tardive, dite "paresseuse", ou lazy-initialization. Par exemple, la classe SystemStartup peut mettre disposition son unique instance de la manire suivante :
public static Factory getFactory() { if (factory == null) factory = new Factory(); // ... return factory; }

Chapitre 8

SINGLETON

81

Exercice 8.2 Pour quelle raison dcideriez-vous de procder linitialisation paresseuse dune instance de singleton plutt que de linitialiser dans la dclaration de son champ ? Quoi quil en soit, le pattern SINGLETON suggre de fournir une mthode publique et statique qui donne accs lobjet SINGLETON. Si cette mthode cre lobjet, elle doit aussi sassurer que seule une instance pourra tre cre.

Singletons et threads
Si vous voulez effectuer linitialisation paresseuse dun singleton dans un environnement multithread, vous devez prendre soin dempcher plusieurs threads dinitialiser le singleton. Dans une telle situation, il ny a aucune garantie quune mthode se termine avant quune autre mthode dans un autre thread commence son excution. Il se pourrait, par exemple, que deux threads tentent dinitialiser un singleton pratiquement en mme temps. Supposez quune mthode dtecte le singleton comme tant null. Si un autre thread dbute ce moment-l, il trouvera galement le singleton null. Les deux mthodes voudront alors linitialiser. Pour empcher ce type de contention, vous devez recourir un mcanisme de verrouillage pour coordonner les mthodes sexcutant dans diffrents threads. Java inclut des fonctionnalits pour supporter le dveloppement multithread, et, plus spciquement, il fournit chaque objet un verrou (lock), une ressource exclusive qui indique que lobjet appartient un thread. Pour garantir quun seul objet initialise un singleton, vous pouvez synchroniser linitialisation par rapport au verrou dun objet appropri. Dautres mthodes ncessitant laccs exclusif au singleton peuvent tre synchronises par rapport au mme verrou. Pour plus dinformations sur la POO avec concurrence, reportez-vous lexcellent ouvrage Concurrent Programming in Java [Lea 2000]. Ce livre suggre une synchronisation sur le verrou qui appartient la classe elle-mme, comme dans le code suivant :
package com.oozinoz.businessCore; import java.util.*; public class Factory { private static Factory factory;

82

Partie II

Patterns dinterface

private static Object classLock = Factory.class; private long wipMoves; private Factory() { wipMoves = 0; } public static Factory getFactory() { synchronized (classLock) { if (factory == null) factory = new Factory(); return factory; } } public void recordWipMove() { // Exercice ! } }

Le code de getFactory() sassure que si un second thread tente une initialisation paresseuse du singleton aprs quun autre thread a commenc la mme initialisation, le second thread devra attendre pour obtenir le verrou dobjet classLock. Une fois quil obtiendra le verrou, il ne dtectera pas de singleton null (tant donn quil ne peut y avoir quune seule instance de la classe, nous pouvons utiliser le seul verrou statique). La variable wipMoves enregistre le nombre de fois que le travail en cours (wip, work in process), progresse. Chaque fois quune caisse arrive sur une autre machine, le sous-systme qui provoque ou enregistre lavancement doit appeler la mthode recordWipMove() du singleton factory. Exercice 8.3 Ecrivez le code de la mthode recordWipMove() de la classe Factory.

Identication de singletons
Les objets uniques ne sont pas chose inhabituelle. En fait, la plupart des objets dans une application assument une responsabilit unique. Pourquoi crer deux objets avec des responsabilits identiques ? De mme, pratiquement chaque classe assure un rle unique. Pourquoi dvelopper deux fois la mme classe ? Dun

Chapitre 8

SINGLETON

83

autre ct, une classe singleton une classe qui nautorise quune instance est relativement rare. Le fait quun objet ou une classe soit unique ne signie pas ncessairement que le pattern SINGLETON est appliqu. Considrez les classes de la Figure 8.1.
Figure 8.1
Quelles classes semblent appliquer SINGLETON?
OurBiggestRocket TopSalesAssociate

java.lang.math

java.lang.System +out:PrintStream

-Math() +pow(a:double,b:double):double

PrintStream PrintSpooler PrinterManager

Exercice 8.4 Pour chaque classe de la Figure 8.1, indiquez sil sagit dune classe singleton et justiez votre rponse.

SINGLETON est probablement le pattern le plus connu, mais il faut y recourir avec prudence car il est facile de mal lutiliser. Ne laissez pas cette technique devenir un moyen pratique de crer des variables globales. Le couplage introduit nest pas beaucoup mieux simplement parce que vous avez utilis un pattern. Minimisez le nombre de classes qui savent quelles travaillent avec un SINGLETON. Il est prfrable pour une classe de simplement savoir quelle a un objet avec lequel travailler et non de connatre les restrictions relatives sa cration. Soyez attentif lemploi

84

Partie II

Patterns dinterface

que vous souhaitez en faire. Par exemple, si vous avez besoin de sous-classes ou de diffrentes versions pour effectuer des tests, SINGLETON ne sera probablement pas appropri car il ny aura pas exactement une seule instance.

Rsum
Le code qui supporte SINGLETON sassure quune classe ne possde quune instance et fournit un point daccs global linstance. Une faon de limplmenter est de procder par initialisation paresseuse dun objet singleton, en linstanciant seulement lorsquil est requis. Dans un environnement multithread, vous devez veiller grer la collaboration des threads qui peuvent accder simultanment aux mthodes et aux donnes dun singleton. Le fait quun objet soit unique nindique pas forcment que le pattern SINGLETON a t utilis. Celui-ci centralise lautorit au niveau dune seule instance de classe en dissimulant le constructeur et en offrant un seul point daccs la mthode de cration de lobjet.

9
OBSERVER
Dordinaire, les clients collectent des informations provenant dun objet en appelant ses mthodes. Toutefois, lorsque lobjet change, un problme survient : comment les clients qui dpendent des informations de lobjet peuvent-ils dterminer que celles-ci ont chang ? Certaines conceptions imputent lobjet la responsabilit dinformer les clients lorsquun aspect intressant de lobjet change. Cette dmarche pose un problme, car cest le client qui sait quels sont les attributs qui lintressent. Lobjet intressant ne doit pas accepter la responsabilit dactualiser le client. Une solution possible est de sarranger pour que le client soit inform lorsque lobjet change et de laisser au client la libert de senqurir ou non du nouvel tat de lobjet. Lobjectif du pattern OBSERVER est de dnir une dpendance du type un-plusieurs (1,n) entre des objets de manire que, lorsquun objet change dtat, tous les objets dpendants en soient notis an de pouvoir ragir conformment.

Un exemple classique : OBSERVER dans les interfaces utilisateurs


Le pattern OBSERVER permet un objet de demander dtre noti lorsquun autre objet change. Lexemple le plus courant dapplication de ce pattern intervient dans les interfaces graphiques utilisateurs, ou GUI. Lorsquun utilisateur clique sur un bouton ou agit sur un curseur, de nombreux objets de lapplication doivent ragir au changement. Les concepteurs de Java ont anticip lintrt de savoir quand un utilisateur provoque un changement au niveau dun composant de linterface graphique.

86

Partie II

Patterns de responsabilit

La forte prsence de OBSERVER dans Swing nous le prouve. Swing se rfre aux clients en tant que listeners et vous pouvez enregistrer autant de listeners que vous le souhaitez pour recevoir les vnements dun objet. Considrez une application typique Oozinoz avec GUI, telle celle illustre Figure 9.1. Cette application permet un ingnieur de tester visuellement les paramtres dterminant la relation entre la pousse (thrust), le taux de combustion (burn rate) et la surface de combustion (burn surface).

Figure 9.1
Les courbes montrent le changement en temps rel lorsque lutilisateur ajuste la variable tPeak avec le curseur.

Lors de la mise feu dune fuse, la portion de combustible qui est expose lair brle et provoque une pousse. De la mise feu au taux de combustion maximal, la surface de combustion, partie de la zone initiale dallumage, augmente pour atteindre la surface totale du combustible. Ce taux maximal se produit au moment t peak. La surface de combustion diminue ensuite mesure que le combustible est consomm. Lapplication de calculs balistiques normalise le temps pour quil soit 0 lors de lallumage et 1 lorsque la combustion sarrte. Aussi, tpeak reprsente un nombre entre 0 et 1. Oozinoz utilise un jeu dquations de calcul du taux de combustion et de la pousse :
taux = 2 5
( t t peak )
2

taux 1 0,3 pousse = 1,7 --------- 0,6

Chapitre 9

OBSERVER

87

Lapplication reprsente Figure 9.1 montre comment t peak inue sur le taux de combustion et la pousse dune fuse. A mesure que lutilisateur dplace le curseur, la valeur de tpeak change et les courbes suivent une autre forme. La Figure 9.2 illustre les classes principales constituant lapplication.
Figure 9.2
Lapplication de calculs balistiques senregistre ellemme pour recevoir les vnements de curseur.
interface ChangeListener

stateChanged( e:ChangeEvent)

JPanel

2 ShowBallistics

BallisticsPanel

burnPanel(): BallisticsPanel slider():JSlider thrustPanel(): BallisticsPanel valueLabel():JLabel stateChanged( e:ChangeEvent)

BallisticsPanel( :BallisticsFunction) setTPeak(tPeak:double)

interface BallisticsFunction

Les classes ShowBallistics et BallisticsPanel sont des membres du package app.observer.ballistics. Linterface BallisticsFunction est un membre du package com.oozinoz.ballistics. Ce package contient aussi une classe utilitaire Ballistics fournissant les instances de BallisticsFunction qui dnissent les courbes pour le taux de combustion et la pousse. Lorsque lapplication initialise le curseur, elle senregistre elle-mme pour en recevoir les vnements. Lorsque la valeur du curseur change, lapplication actualise les panneaux dafchage (Panel) des courbes ainsi que ltiquette (Label) afchant la valeur de tpeak.

88

Partie II

Patterns de responsabilit

Exercice 9.1 Compltez les mthodes slider() et stateChanged() pour ShowBallistics de manire que les panneaux dafchage et la valeur de tpeak retent la position du curseur.
public JSlider slider() { if (slider == null) { slider = new JSlider(); sliderMax = slider.getMaximum(); sliderMin = slider.getMinimum(); slider.addChangeListener( ?? ); slider.setValue(slider.getMinimum()); } return slider; } public void stateChanged(ChangeEvent e) { double val = slider.getValue(); double tp = (val - sliderMin) / (sliderMax - sliderMin); burnPanel(). ?? ( ?? ); thrustPanel(). ?? ( ?? ); valueLabel(). ?? ( ?? ); }

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.


La classe ShowBallistics actualise les objets pour les deux panneaux dafchage et la valeur de tpeak, qui dpend de la valeur du curseur. Cest une pratique frquente et pas ncessairement mauvaise, mais notez quelle dfait totalement lobjectif de OBSERVER. Swing applique OBSERVER pour que le curseur nait pas savoir quels sont les clients intresss par son tat. Lapplication ShowBallistics nous place toutefois dans la situation que voulions viter, savoir : un seul objet, lapplication, sait quels objets actualiser et se charge dmettre les interrogations appropries au lieu de laisser chaque objet senregistrer lui-mme de manire individuelle. Pour crer un OBSERVER dune plus grande granularit, vous pouvez apporter quelques changements au code pour laisser chaque composant intress senregistrer lui-mme pour recevoir les vnements de changement du curseur. Dans cette conception, vous pouvez dplacer les appels de addChangeListener() se trouvant dans la mthode slider() vers les constructeurs des composants dpendants :

Chapitre 9

OBSERVER

89

public BallisticsPanel2( BallisticsFunction func, JSlider slider) { this.func = func; this.slider = slider; slider.addChangeListener(this); }

Exercice 9.2 Produisez un nouveau diagramme de classes pour une conception laissant chaque objet intress senregistrer pour recevoir les vnements du curseur. Veillez tenir compte du champ afchant la valeur du curseur.

Lorsque le curseur change, lobjet BallisticsPanel2 en est averti. Le champ tPeak recalcule sa valeur et se redessine :
public void stateChanged(ChangeEvent e) { double val = slider.getValue(); double max = slider.getMaximum(); double min = slider.getMinimum(); tPeak = (val - min) / (max - min); repaint(); }

Cette nouvelle conception donne lieu un nouveau problme. Chaque objet intress senregistre et sactualise lors des changements du curseur. Cette rpartition de la responsabilit est bonne, mais chaque composant qui est lcoute des vnements du curseur doit recalculer la valeur de tPeak. En particulier, si vous utilisez une classe BallisticsLabel2 comme dans la solution de lExercice 9.2 , sa mthode stateChanged() sera presque identique la mthode stateChanged() de BallisticsPanel2. Pour rduire ce code dupliqu, nous pouvons extraire un objet de domaine sous-jacent partir de la prsente conception. Nous pouvons simplier le systme en introduisant une classe Tpeak qui contiendra la valeur de temps critique. Lapplication restera alors lcoute des vnements du curseur et actualisera un objet Tpeak auquel seront attentifs les autres composants intresss. Cette approche devient une conception MVC (Modle-Vue-Contrleur) (voir [Buschmann et al. 1996] pour un traitement en dtail de lapproche MVC).

90

Partie II

Patterns de responsabilit

Modle-Vue-Contrleur
A mesure que les applications et les systmes augmentent de taille, il est important de rpartir toujours davantage les responsabilits pour que les classes et les packages restent dune taille sufsamment petite pour faciliter la maintenance. La triade Modle-Vue-Contrleur spare un objet intressant, le modle, des lments de GUI qui le reprsentent et le manipulent, la vue et le contrleur. Java gre cette sparation au moyen de listeners, mais comme le montre la section prcdente, toutes les conceptions recourant des listeners ne suivent pas ncessairement une approche MVC. Les versions initiales de lapplication ShowBallistics combinent la logique intelligente dune interface GUI dapplication et des informations balistiques. Vous pouvez rduire ce code par une approche MVC pour redistribuer les responsabilits de lapplication. Dans le processus de recodage, la classe ShowBallistics rvise garde les vues et les contrleurs dans ses lments de GUI. Lide des crateurs de MVC tait que lapparence dun composant (la vue) pouvait tre spare de ce qui lanimait (le contrleur). Dans la pratique, lapparence dun composant de GUI et ses fonctionnalits supportant linteraction utilisateur sont troitement couples, et lemploi typique de Swing ne spare pas les vues des contrleurs si vous vous plongez davantage dans les rouages internes de ces concepts dans Swing, vous verrez merger cette sparation. La valeur de MVC rside dans le fait dextraire le modle dune application pour le placer dans un domaine propre. Le modle dans lapplication ShowBallistics est la valeur tPeak. Pour recoder avec lapproche MVC, nous pourrions introduire une classe Tpeak qui contiendrait cette valeur de temps crte et autoriser les listeners intresss senregistrer pour recevoir les vnements de changement. Une telle classe pourrait ressembler lextrait suivant :
package app.observer.ballistics3; import java.util.Observable; public class Tpeak extends Observable { protected double value; public Tpeak(double value) { this.value = value; } public double getValue() { return value;

Chapitre 9

OBSERVER

91

} public void setValue(double value) { this.value = value; setChanged(); notifyObservers(); } }

Si vous deviez rviser ce code chez Oozinoz, un point essentiel serait soulev : presque aucune portion de ce code ne se rapporte au moment o le taux de combustion atteint sa valeur crte. En fait, cet extrait ressemble un outil relativement gnrique pour contenir une valeur et pour alerter des listeners lorsquelle change. Nous pourrions modier le code pour liminer ce caractre gnrique, mais examinons tout dabord une conception rvise utilisant la classe Tpeak. Nous pouvons maintenant laborer une conception dans laquelle lapplication reste attentive au curseur et tous les autres composants restent lcoute de lobjet Tpeak. Lorsque le curseur est dplac, lapplication change la valeur dans lobjet Tpeak. Les panneaux dafchage et le champ de valeur sont lcoute de cet objet et sactualisent lorsquil change. Les classes BurnRate et Thrust emploient lobjet Tpeak pour le calcul de leurs fonctions, mais elles nont pas besoin dcouter les vnements (cest--dire de senregistrer cet effet). Exercice 9.3 Crez un diagramme de classes montrant lapplication dpendant du curseur alors que les panneaux dafchage et le champ de valeur dpendent dun objet Tpeak.

Cette conception permet de neffectuer quune seule fois le travail de traduction de la valeur du curseur en valeur de temps crte. Lapplication actualise un seul objet Tpeak, et tous les objets de GUI intresss par un changement peuvent interroger lobjet pour en connatre la nouvelle valeur. La classe Tpeak ne fait pas que conserver une valeur. Aussi essayons-nous de recoder lapplication pour crer une classe conteneur de valeur. De plus, il est possible quun nombre observ, tel quune valeur de temps crte, ne soit pas une valeur isole mais plutt lattribut dun objet de domaine. Par exemple, le temps crte est un attribut dun moteur de fuse. Nous pouvons tenter damliorer notre conception

92

Partie II

Patterns de responsabilit

pour sparer les classes, avec une classe permettant aux objets de GUI dobserver les objets de domaine. Lorsque vous dcidez de sparer des objets de GUI dobjets de domaine, ou dobjets mtiers (business object), vous pouvez crer des couches de code. Une couche est un groupe de classes ayant des responsabilits similaires, souvent rassembles dans un seul package Java. Les couches suprieures, telles quune couche GUI, dpendent gnralement seulement de classes situes dans des couches de niveau gal ou infrieur. Le codage en couches demande gnralement davoir une dnition claire des interfaces entre les couches, telles quentre une GUI et les objets mtiers quelle reprsente. Vous pouvez rorganiser les responsabilits du code de ShowBallistics pour obtenir un systme en couches, comme le montre la Figure 9.3.
Figure 9.3
En crant une classe Tpeak observable, vous pouvez sparer la logique mtier et la couche GUI.
Couche GUI Couche mtier Observer Observer

BallisticsPanel

BallisticsLabel

Observable

Tpeak

addObserver(o:Observer) notifyObservers() setChanged()

getValue() setValue(value:double)

La conception illustre Figure 9.3 cre une classe Tpeak pour modliser la valeur tpeak critique pour les rsultats des quations balistiques afchs par lapplication. Les classes BallisticsPanel et BallisticsLabel dpendent de Tpeak. Plutt que de laisser lobjet Tpeak la responsabilit dactualiser les lments de GUI, la conception applique le pattern OBSERVER pour que les objets intresss puissent senregistrer pour tre notis de tout changement de Tpeak. Les bibliothques de classes Java offrent un support en fournissant une classe Observable et une

Chapitre 9

OBSERVER

93

interface Observer contenues dans le package java.util package. La classe Tpeak peut driver une sous-classe Observable et actualiser ses objets "observateurs" lorsque sa valeur change :
public void setValue(double value) { this.value = value; setChanged(); notifyObservers(); }

Notez que vous devez appeler setChanged() de faon que la mthode notifyObservers(), hrite dObservable, signale le changement. La mthode notifyObservers() invoque la mthode update() de chaque observateur enregistr. La mthode update() est une exigence pour les classes implmentant linterface Observer, comme illustr Figure 9.4.
Figure 9.4
Un objet BallisticsLabel est un Observer. Il peut senregistrer auprs dun objet Observable comme objet intress pour que la mthode update() de lobjet label soit appele lorsque lobjet Observable change.
interface Observer BallisticsLabel( tPeak:Tpeak) update( o:Observable, arg:Object) BallisticsLabel

update( o:Observable, arg:Object)

Un objet BallisticsLabel na pas besoin de conserver une rfrence vers lobjet Tpeak quil observe. Au lieu de cela, le constructeur de BallisticsLabel peut senregistrer pour obtenir les mises jour lorsque lobjet Tpeak change. La mthode update() de lobjet BallisticsLabel recevra lobjet Tpeak en tant quargument Observable. La mthode peut transtyper (cast) largument en Tpeak, extraire la nouvelle valeur, modier le champ de valeur et redessiner lcran. Exercice 9.4 Rdigez le code complet pour BallisticsLabel.java.

94

Partie II

Patterns de responsabilit

La nouvelle conception de lapplication de calcul balistique spare lobjet mtier des lments de GUI qui le reprsentent. Deux exigences doivent tre respectes pour quelle fonctionne. 1. Les implmentations de Observer doivent senregistrer pour signaler leur intrt et doivent sactualiser elles-mmes de faon correcte, souvent en incluant lactualisation de lafchage. 2. Les sous-classes de Observable doivent notier les objets intresss observateurs lorsque leurs valeurs changent. Ces deux tapes dnissent la plupart des interactions dont vous avez besoin entres les diffrentes couches de lapplication balistique. Il vous faut aussi prvoir un objet Tpeak qui change conformment lorsque la position du curseur change. Vous pouvez pour cela instancier une sous-classe anonyme de ChangeListener.

Exercice 9.5 Supposez que tPeak soit une instance de Tpeak et un attribut de la classe ShowBallistics3. Compltez le code de ShowBallistics3.slider() de faon que le changement du curseur actualise tPeak.
public JSlider slider() { if (slider == null) { slider = new JSlider(); sliderMax = slider.getMaximum(); sliderMin = slider.getMinimum(); slider.addChangeListener ( new ChangeListener() { // Exercice ! } ); slider.setValue(slider.getMinimum()); } return slider; }

Chapitre 9

OBSERVER

95

Lorsque vous appliquez lapproche MVC, le ux des vnements peut sembler indirect. Un mouvement de curseur dans lapplication de calculs balistiques provoque lactualisation dun objet Tpeak par un objet ChangeListener. En retour, un changement dans lobjet Tpeak notie les objets dafchage (champ et panneaux) qui actualisent leur afchage. Le changement est rpercut de la couche GUI la couche mtier puis nouveau vers la couche GUI.

:JSlider

:ChangeListener Couche GUI Couche mtier :Tpeak

:BallisticsLabel

?? ??

??

Figure 9.5
Lapproche MVC demande de passer les messages de la couche GUI vers la couche mtier puis nouveau vers la couche GUI.

Le bnce dune telle conception en couches rside dans la valeur de linterface et dans le niveau dindpendance que vous obtenez entre les couches. Ce codage en couches est une distribution en couches des responsabilits, ce qui produit un code plus simple maintenir. Par exemple, dans notre exemple dapplication de calculs balistiques, vous pouvez ajouter une seconde GUI, peut-tre pour un quipement portable, sans avoir changer les classes de la couche objets mtiers. De mme, vous pourriez ajouter dans cette dernire une nouvelle source de changement qui actualise un objet Tpeak. Dans ce cas, le mcanisme OBSERVER dj en place met automatiquement jour les objets de la couche GUI.

96

Partie II

Patterns de responsabilit

Cette conception en couches rend aussi possible lexcution de diffrentes couches sur diffrents ordinateurs. Une couche ou un ensemble de couches sexcutant sur un ordinateur constitue un niveau dans un systme multiniveau (n-tier). Une conception multiniveau peut rduire la quantit de code devant tre excute sur lordinateur de lutilisateur nal. Elle vous permet aussi dapporter des changements dans les classes mtiers sans avoir changer le logiciel sur les machines des utilisateurs, ce qui simplie grandement le dploiement. Toutefois, lchange de messages entre ordinateurs a son cot et le dploiement en environnement multiniveau doit tre fait judicieusement. Par exemple, vous ne pourrez probablement pas vous permettre de faire attendre lutilisateur pendant que les vnements de dlement transitent entre son ordinateur et le serveur. Dans ce cas, vous devrez probablement laisser le dlement se produire sur la machine de lutilisateur, puis concevoir sous forme dune autre action utilisateur distincte la validation dune nouvelle valeur de temps crte. En bref, OBSERVER supporte larchitecture MVC, ce qui promeut la conception en couches et saccompagne de nombreux avantages pour le dveloppement et le dploiement de logiciels.

Maintenance dun objet Observable


Vous ne pourrez pas toujours crer la classe que vous voulez pour guetter les changements dune sous-classe de Observable. En particulier, votre classe peut dj tre une sous-classe de quelque chose dautre que Object. Dans ce cas, vous pouvez associer votre classe un objet Observable et faire en sorte quelle lui transmette les appels de mthodes essentiels. La classe Component dans java.awt suit cette approche mais utilise un objet PropertyChangeSupport la place dun objet Observable. La classe PropertyChangeSupport est semblable la classe Observable, mais elle fait partie du package java.beans. LAPI JavaBeans permet la cration de composants rutilisables. Elle trouve sa plus grande utilit dans le dveloppement de composants de GUI, mais vous pouvez certainement lappliquer dautres ns. La classe Component emploie un objet PropertyChangeSupport pour permettre aux objets observateurs intresss de senregistrer et de recevoir une notication de changement des proprits de champs, de panneaux, et dautres lments de GUI. La Figure 9.6 montre la relation qui existe entre la classe Component de java.awt et la classe PropertyChangeSupport.

Chapitre 9

OBSERVER

97

La classe PropertyChangeSupport illustre un problme quil vous faudra rsoudre lors de lemploi du pattern OBSERVER, savoir, le niveau de dtails fournir par la classe observe pour indiquer ce qui a chang. Cette classe utilise une approche push, o le modle renseigne sur ce qui sest produit dans PropertyChangeSupport, la notication indique le changement de la proprit, dune ancienne valeur une nouvelle valeur. Une autre option est lapproche pull, o le modle signale aux objets observateurs quil a chang, mais ceux-ci doivent interroger le modle pour savoir de quelle manire. Les deux approches peuvent tre appropries. Lapproche push peut signier davantage de travail de dveloppement et associe troitement les objets observateurs lobjet observ, mais offre la possibilit de meilleures performances. La classe Component duplique une partie de linterface de la classe PropertyChangeSupport. Ces mthodes dans Component transmettent chacune lappel de message vers une instance de la classe PropertyChangeSupport.

Component

PropertyChangeSupport

addPropertyChangeListener( l:PropertyChangeListener) firePropertyChange( propertyName:String, oldValue:Object, newValue:Object) removePropertyChangeListener( l:PropertyChangeListener) ...

addPropertyChangeListener( l:PropertyChangeListener) firePropertyChange( propertyName:String, oldValue:Object, newValue:Object) removePropertyChangeListener( l:PropertyChangeListener)

interface PropertyChangeListener

propertyChange( e:PropertyChangeEvent)

Figure 9.6
Un objet Component renseigne un objet PropertyChangeSupport qui renseigne un ensemble de listeners.

98

Partie II

Patterns de responsabilit

??

??

BallisticsPanel

BallisticsLabel

Tpeak

PropertyChangeSupport

?? ?? ?? ?? ??

Figure 9.7
Un objet mtier Tpeak peut dlguer les appels qui affectent les listeners un objet Property-

ChangeSupport.

Exercice 9.7 Compltez le diagramme de classes de la Figure 9.7 pour que Tpeak utilise un objet PropertyChangeSupport an de grer des listeners. Que vous utilisiez Observer, PropertyChangeSupport ou une autre classe pour appliquer le pattern OBSERVER, limportant est de dnir une dpendance un-plusieurs entre des objets. Lorsque ltat dun objet change, tous les objets dpendants en sont avertis et sont actualiss automatiquement. Cela limite la responsabilit et facilite la maintenance des objets intressants et de leurs observateurs intresss.

Chapitre 9

OBSERVER

99

Rsum
Le pattern OBSERVER apparat frquemment dans les applications avec GUI et constitue un pattern fondamental dans les bibliothques de GUI Java. Avec ces composants, vous navez jamais besoin de modier ou de driver une sous-classe dune classe de composant simplement pour communiquer ses vnements dautres objets intresss. Pour de petites applications, une pratique courante consiste nenregistrer quun seul objet, lapplication, pour recevoir les vnements dune GUI. Il ny a pas de problme inhrent cette approche, mais sachez quelle inverse la rpartition des responsabilits que vise OBSERVER. Pour une grande GUI, envisagez la possibilit de passer une conception MVC, en permettant chaque objet intress de grer son besoin dtre noti au lieu dintroduire un objet central mdiateur. Lapproche MVC vous permet aussi dassocier avec davantage de souplesse diverses couches de lapplication, lesquelles peuvent alors changer de faon indpendante et tre excutes sur des machines diffrentes.

10
MEDIATOR
Le dveloppement orient objet ordinaire distribue la responsabilit aussi loin que possible, avec chaque objet accomplissant sa tche indpendamment des autres. Par exemple, le pattern OBSERVER supporte cette distribution en limitant la responsabilit dun objet que dautres objets trouvent intressant. Le pattern SINGLETON rsiste la distribution de la responsabilit et vous permet de la centraliser au niveau de certains objets que les clients localisent et rutilisent. A linstar de SINGLETON, le pattern MEDIATOR centralise la responsabilit mais pour un ensemble spcique dobjets plutt que pour tous les clients dans un systme. Lorsque les interactions entre les objets reposent sur une condition complexe impliquant que chaque objet dun groupe connaisse tous les autres, il est utile dtablir une autorit centrale. La centralisation de la responsabilit est galement utile lorsque la logique entourant les interactions des objets en relation est indpendante de lautre comportement des objets. Lobjectif du pattern MEDIATOR est de dnir un objet qui encapsule la faon dont un ensemble dobjets interagissent. Cela promeut un couplage lche, vitant aux objets davoir se rfrer explicitement les uns aux autres, et permet de varier leur interaction indpendamment.

Un exemple classique : mdiateur de GUI


Cest probablement lors du dveloppement dune application GUI que vous rencontrerez le plus le pattern MEDIATOR. Le code dune telle application tend devenir volumineux, demandant tre refactoris en dautres classes. La classe ShowFlight dans le Chapitre 4, consacr au pattern FACADE, remplissait initialement

102

Partie II

Patterns de responsabilit

trois rles. Avant quelle ne soit refactorise, elle servait de panneau dafchage, dapplication GUI complte et de calculateur de trajectoire de vol. La refactorisation a permis de simplier lapplication invoquant lafchage du panneau de trajectoire, la ramenant seulement quelques lignes de code. Toutefois, les grosses applications peuvent conserver leur complexit aprs ce type de refactorisation, mme si elles nincluent que la logique qui cre les composants et dnit leur interaction. Considrez lapplication de la Figure 10.1.

Figure 10.1
Cette application laisse lutilisateur actualiser manuellement lemplacement dun bac de produit chimique.

Oozinoz stocke les produits chimiques dans des bacs (tub) en plastique. Les machines lisent les codes-barres sur les bacs pour garder trace de leur emplacement dans lusine. Parfois, une correction manuelle est ncessaire, notamment lorsquun employ dplace un bac au lieu dattendre quun robot le transfre. La Figure 10.1 prsente une nouvelle application partiellement dveloppe qui permet lutilisateur de spcier la machine au niveau de laquelle un bac se situe. Dans lapplication MoveATub, du package app.mediator.moveATub, lorsque lutilisateur slectionne une des machines dans la liste de gauche, la liste de bacs change pour afcher ceux prsents sur la machine en question. Il peut ensuite slectionner un des bacs, choisir une machine cible, et cliquer sur le bouton Do it! pour actualiser lemplacement du bac. La Figure 10.2 prsente un extrait de la classe de lapplication. Le dveloppeur de cette application la cre initialement laide dun assistant et a commenc la refactoriser. Environ la moiti des mthodes de MoveATub existent

Chapitre 10

MEDIATOR

103

MoveATub

MoveATub() assignButton():JButton actionPerformed() boxes():List boxList():JList machineList():JList tubList():ListView valueChanged() labeledPanel():JPanel tubList():ListView updateTubList(:String) main()

interface ListSelectionListener

interface ActionListener

Figure 10.2
La classe MoveATub combine des mthodes de cration de composants, de gestion dvnements, et de base de donnes ctive.

pour procder une initialisation paresseuse des variables contenant les composants GUI de lapplication. La mthode assignButton() est un exemple typique :
private JButton assignButton() { if (assignButton == null) { assignButton = new JButton("Do it!"); assignButton.setEnabled(false); assignButton.addActionListener(this); } return assignButton; }

Le programmeur a dj limin les valeurs codes en dur gnres par lassistant pour spcier lemplacement et la taille du bouton. Mais un problme plus immdiat est que la classe MoveATub comporte un grand nombre de mthodes ayant des utilits diffrentes. La plupart des mthodes statiques fournissent une base de donnes ctive de noms de bacs et de noms de machines. Le dveloppeur envisage de remplacer lapproche consistant utiliser uniquement des noms par lemploi dobjets Tub et Machine.

104

Partie II

Patterns de responsabilit

La majorit des autres mthodes contient la logique de gestion des vnements de lapplication. Par exemple, la mthode valueChanged() dtermine si le bouton dassignation a t activ :
public void valueChanged(ListSelectionEvent e) { // ... assignButton().setEnabled( ! tubList().isSelectionEmpty() && ! machineList().isSelectionEmpty()); }

Le dveloppeur pourrait placer la mthode valueChanged() et les autres mthodes de gestion dvnements dans une classe mdiateur distincte. Il convient de noter que le pattern MEDIATOR est dj luvre dans la classe MoveATub: les composants ne sactualisent pas directement les uns les autres. Par exemple, ni la machine ni les composants liste nactualisent directement le bouton dassignation. A la place, lapplication MoveATub enregistre des listeners pour les vnements de slection puis actualise le bouton, selon les lments slectionns dans les deux listes. Dans cette application, un objet MoveATub agit en tant que mdiateur, recevant les vnements et dispatchant les actions correspondantes. Les bibliothques de classes Java, ou JCL (Java Class Libraries), vous incitent utiliser un mdiateur mais nimposent aucunement que lapplication soit son propre mdiateur. Au lieu de mlanger dans une mme classe des mthodes de cration de composants, des mthodes de gestion dvnements et des mthodes de base de donnes ctive, il serait prfrable de les placer dans des classes avec des spcialisations distinctes. La refactorisation donne au mdiateur une classe propre, vous permettant de le dvelopper et de vous concentrer dessus sparment. Lorsque lapplication refactorise sexcute, les composants passent les vnements un objet MoveATubMediator. Le mdiateur peut avoir une action sur des objets non-GUI, par exemple pour actualiser la base de donnes lorsquune assignation a lieu. Il peut aussi rappeler des composants GUI, par exemple pour dsactiver le bouton lissue de lassignation. Les composants GUI pourraient appliquer le pattern MEDIATOR automatiquement, signalant un mdiateur lorsque des vnements surviennent plutt que de prendre la responsabilit dactualiser directement dautres composants. Les applications GUI donnent probablement lieu lexemple le plus courant de MEDIATOR, mais il existe dautres situations o vous pourriez vouloir introduire un mdiateur.

Chapitre 10

MEDIATOR

105

MoveATub2

MoveATubMediator

NameBase

Figure 10.3
Sparation des mthodes de cration de composants, des mthodes de gestion dvnements et des mthodes de base de donnes ctive de lapplication.

Exercice 10.2 Dessinez un diagramme illustrant ce qui se produit lorsque lutilisateur clique sur le bouton Do it!. Montrez quels objets sont daprs vous les plus importants ainsi que les messages changs entre ces objets. Lorsque linteraction dun ensemble dobjets est complexe, vous pouvez centraliser la responsabilit de cette interaction dans un objet mdiateur qui reste extrieur au groupe. Cela promeut le couplage lche (loose coupling), cest--dire une rduction de la responsabilit que chaque objet entretient vis--vis de chaque autre. Grer cette interaction dans une classe indpendante prsente aussi lavantage de simplier et de standardiser les rgles dinteraction. La valeur dun mdiateur apparat de manire vidente lorsque vous avez besoin de grer lintgrit relationnelle.

106

Partie II

Patterns de responsabilit

Mdiateur dintgrit relationnelle


Le paradigme orient objet doit sa puissance en partie au fait quil permet de reprsenter aisment au moyen dobjets Java les relations entre des objets du monde rel. Toutefois, la capacit dun modle objet Java reter le monde rel se heurte deux limitations. Premirement, les objets rels varient avec le temps et Java noffre aucun support intgr pour cela. Par exemple, les instructions dassignation liminent toute valeur prcdente au lieu de la mmoriser, comme un tre humain le ferait. Deuximement, dans le monde rel, les relations sont aussi importantes que les objets, alors quelles ne bncient que dun faible support dans les langages orients objet actuels, Java y compris. Par exemple, il ny a pas de support intgr pour le fait que si la machine Star Press 2402 se trouve dans la trave 1, la trave 1 doit contenir la machine Star Press 2402. En fait, de telles relations risquent dtre ignores, do lintrt dappliquer le pattern MEDIATOR. Considrez les bacs (tub) en plastique dOozinoz. Ces bacs sont toujours assigns une certaine machine. Vous pouvez modliser cette relation au moyen dune table, comme lillustre le Tableau 10.1.
Tableau 10.1 : Enregistrer les informations relationnelles dans une table prserve lintgrit relationnelle

Bac

Machine

T305 T308 T377 T379 T389 T001 T002

StarPress-2402 StarPress-2402 ShellAssembler-2301 ShellAssembler-2301 ShellAssembler-2301 Fuser-2101 Fuser-2101

Le Tableau 10.1 illustre la relation entre les bacs et les machines, cest--dire leur positionnement rciproque. Mathmatiquement, une relation est un sous-ensemble de toutes les paires ordonnes dobjets, tel quil y a une relation des bacs vers les machines et une relation des machines vers les bacs. Lunicit des valeurs de la

Chapitre 10

MEDIATOR

107

colonne Bac garantit quaucun bac ne peut apparatre sur deux machines la fois. Voyez lencadr suivant pour une dnition plus stricte de la cohrence relationnelle dans un modle objet.
Intgrit relationnelle Un modle objet prsente une cohrence relationnelle si chaque fois que lobjet a pointe vers lobjet b, lobjet b pointe vers lobjet a. Pour une dnition plus rigoureuse, considrez deux classes, Alpha et Beta. A reprsente lensemble des objets qui sont des instances de la classe Alpha, et B reprsente lensemble des objets qui sont des instances de la classe Beta. a et b sont donc des membres respectivement de A et de B, et la paire ordonne (a, b) indique que lobjet a A possde une rfrence vers lobjet b B. Cette rfrence peut soit tre directe soit faire partie dun ensemble de rfrences, comme lorsque lobjet a possde un objet List qui inclut b. Le produit cartsien A B est lensemble de toutes les paires ordonnes possibles (a, b) avec a A et b B. Les ensembles A et B autorisent les deux produits cartsiens A B et B A.

Une relation de modle objet sur A et B est le sous-ensemble de A B qui existe dans un modle objet. AB reprsente ce sous-ensemble, et BA reprsente le sous-ensemble B A qui existe dans le modle. Toute relation binaire R A B possde un inverse R1 B A dni par : (b, a) R1 si et seulement si (a, b) R

Linverse de AB fournit lensemble des rfrences qui doivent exister de B vers les instances de A lorsque le modle objet est cohrent. Autrement dit, les instances des classes Alpha et Beta sont cohrentes relationnellement si et seulement si BA est linverse de AB.

Lorsque vous enregistrez les informations relationnelles des bacs et des machines dans une table, vous pouvez garantir que chaque bac se trouve sur une seule machine la fois en appliquant comme restriction quil napparaisse quune seule fois dans la colonne Bac. Une faon de procder est de dnir cette colonne comme cl primaire de la table dans une base de donnes relationnelle. Avec ce modle, qui rete la ralit, un bac ne peut pas apparatre sur deux machines en mme temps : (b, a) R1 si et seulement si (a, b) R. Un modle objet ne peut pas garantir lintgrit relationnelle aussi facilement quun modle relationnel. Considrez lapplication MoveATub. Comme voqu prcdemment, son concepteur prvoit dabandonner lemploi de noms au prot dobjets Tub et Machine. Lorsquun bac se trouve prs dune machine, lobjet qui le reprsente possde une rfrence vers lobjet reprsentant la machine. Chaque objet Machine

108

Partie II

Patterns de responsabilit

possde une collection dobjets Tub reprsentant les bacs situs prs de la machine. La Figure 10.4 illustre un modle objet typique.
Figure 10.4
Un modle objet distribue les informations sur les relations.
T305 StarPress-2402 T377 T308 Assembler-2301

T379

T389

T001 Fuser-2101 T002

Les doubles ches de cette gure mettent en vidence le fait que les bacs ont connaissance des machines et vice versa. Les informations sur cette relation bac/ machine sont maintenant distribues travers de nombreux objets au lieu de gurer dans une table centrale, ce qui la rend plus difcile grer et fait quelle se prte bien lapplication du pattern MEDIATOR. Considrez une anomalie survenue chez Oozinoz lorsquun dveloppeur a commenc modliser une nouvelle machine incluant un lecteur de codes-barres pour identier les bacs. Aprs scannage dun bac t pour obtenir son identiant, son emplacement est dni comme tant sur la machine m au moyen du code suivant :
// Renseigne le bac sur la machine et inversement t.setMachine(m); m.addTub(t);

Le moyen le plus simple de garantir lintgrit relationnelle est de replacer les informations relationnelles dans une seule table gre par un objet mdiateur. Au lieu que les machines aient connaissance des bacs et inversement, il faut donner ces objets une rfrence vers un mdiateur qui soccupe de grer la table. Cette table peut tre une instance de la classe Map (du package java.util). La Figure 10.6 prsente un diagramme de classe incluant un mdiateur.

Chapitre 10

MEDIATOR

109

T305 StarPress-2402 T377 T308 ShellAssembler-2301

T379

T389

T001 Fuser-2101 T002

Figure 10.5
Une fois complt, ce diagramme mettra en vidence lerreur dans le code qui actualise lemplacement du bac.

Figure 10.6
Les objets Tub et Machine sappuient sur un mdiateur pour contrler la relation entre les bacs et les machines.
Tub mediator:TubMediator Machine mediator:TubMediator addTub(t:Tub) getTubs():Set

getLocation():Machine setLocation(m:Machine)

TubMediator -tubToMachine:Map

getTubs(m:Machine):Set getMachine(t:Tub):Machine set(t:Tub,m:Machine)

110

Partie II

Patterns de responsabilit

La classe Tub possde un attribut demplacement qui permet denregistrer la machine proximit de laquelle se trouve un bac. Le code garantit quun bac ne peut tre qu un seul endroit la fois, utilisant un objet TubMediator pour grer la relation bac/machine :
package com.oozinoz.machine; public class Tub { private String id; private TubMediator mediator = null; public Tub(String id, TubMediator mediator) { this.id = id; this.mediator = mediator; }

public Machine getLocation() { return mediator.getMachine(this); } public void setLocation(Machine value) { mediator.set(this, value); } public String toString() { return id; } public int hashCode() { // ... } public boolean equals(Object obj) { // ... } }

La mthode setLocation() de la classe Tub utilise un mdiateur pour actualiser lemplacement dun bac, lui dlguant la responsabilit de prserver lintgrit relationnelle. Cette classe implmente les mthodes hashCode() et equals() de sorte que les objets Tub puissent tre correctement stocks dans une table de hachage. Voici les dtails du code :
public int hashCode() { return id.hashCode(); } public boolean equals(Object obj) {

Chapitre 10

MEDIATOR

111

if (obj == this) return true; if (obj.getClass() != Tub.class) return false; Tub that = (Tub) obj; return id.equals(that.id); }

La classe TubMediator utilise un objet Map pour stocker la relation bac/machine. Le mdiateur peut ainsi garantir que le modle objet nautorise jamais deux machines possder le mme bac :
public class TubMediator { protected Map tubToMachine = new HashMap(); public Machine getMachine(Tub t) { // Exercice ! } public Set getTubs(Machine m) { Set set = new HashSet(); Iterator i = tubToMachine.entrySet().iterator(); while (i.hasNext()) { Map.Entry e = (Map.Entry) i.next(); if (e.getValue().equals(m)) set.add(e.getKey()); } return set; } public void set(Tub t, Machine m) { // Exercice ! } }

Exercice 10.4 Ecrivez le code des mthodes getMachine() et set() de la classe TubMediator.

Plutt que dintroduire une classe mdiateur, vous pourriez garantir quun bac ne se trouve jamais sur deux machines en mme temps en plaant la logique directement dans les classes Tub et Machine. Toutefois, cette logique prserve lintgrit relationnelle et na pas grand-chose voir avec le fonctionnement des bacs et des machines. Elle peut aussi tre source derreurs. Une erreur possible serait de dplacer

112

Partie II

Patterns de responsabilit

un bac vers une autre machine, en actualisant ces deux objets mais pas la machine prcdente. Lemploi dun mdiateur permet dencapsuler dans une classe indpendante la logique dnissant la faon dont les objets interagissent. Au sein du mdiateur, il est facile de sassurer que le fait de changer lemplacement dun objet Tub loigne automatiquement le bac de la machine sur laquelle il se trouvait. Le code de test JUnit suivant, du package TubTest.java, prsente ce comportement :
public void testLocationChange() { TubMediator mediator = new TubMediator(); Tub t = new Tub("T403", mediator); Machine m1 = new Fuser(1001, mediator); Machine m2 = new Fuser(1002, mediator); t.setLocation(m1); assertTrue(m1.getTubs().contains(t)); assertTrue(!m2.getTubs().contains(t)); t.setLocation(m2); assertFalse(m1.getTubs().contains(t)); assertTrue(m2.getTubs().contains(t)); }

Lorsque vous disposez dun modle objet qui nest pas li une base de donnes relationnelle, vous pouvez utiliser des mdiateurs pour prserver lintgrit relationnelle de votre modle. Coner la gestion de la relation des mdiateurs permet ces classes de se spcialiser dans cette prservation. Exercice 10.5 Pour ce qui est dextraire une logique dune classe ou dune hirarchie existante an de la placer dans une nouvelle classe, MEDIATOR ressemble dautres patterns. Citez deux autres patterns pouvant impliquer une telle refactorisation.

Rsum
Le pattern MEDIATOR promeut le couplage lche, vitant des objets en relation de devoir se rfrer explicitement les uns aux autres. Il intervient le plus souvent dans le dveloppement dapplications GUI, lorsque vous voulez viter davoir grer la complexit lie lactualisation mutuelle dobjets. Larchitecture de Java vous pousse dans cette direction, vous encourageant dnir des objets qui enregistrent

Chapitre 10

MEDIATOR

113

des listeners pour les vnements GUI. Si vous dveloppez des interfaces utilisateur avec Java, vous appliquez probablement ce pattern. Bien que ce chapitre puisse vous inciter utiliser le pattern MEDIATOR lors de la cration dune interface GUI, sachez que Java ne vous oblige pas extraire cette mdiation de la classe de lapplication. Mais cela peut nanmoins simplier votre code. Le mdiateur peut ainsi se concentrer sur linteraction entre les composants GUI, et la classe dapplication peut se concentrer sur la construction des composants. Dautres situations se prtent lintroduction dun objet mdiateur. Par exemple, vous pourriez en avoir besoin pour centraliser la responsabilit de prserver lintgrit relationnelle dans un modle objet. Vous pouvez appliquer MEDIATOR chaque fois que vous devez dnir un objet qui encapsule la faon dont un ensemble dobjets interagissent.

11
PROXY
Un objet ordinaire fait sa part de travail pour supporter linterface publique quil annonce. Il peut nanmoins arriver quun objet lgitime ne soit pas en mesure dassumer cette responsabilit ordinaire. Cela peut se produire lorsque lobjet met beaucoup de temps se charger, lorsquil sexcute sur un autre ordinateur, ou lorsque vous devez intercepter des messages qui lui sont destins. Dans de telles situations, un objet proxy peut prendre cette responsabilit vis--vis dun client et transmettre les requtes au moment voulu lobjet cible sous-jacent. Lobjectif du pattern PROXY est de contrler laccs un objet en fournissant un intermdiaire pour cet objet.

Un exemple classique : proxy dimage


Un objet proxy possde gnralement une interface qui est quasiment identique celle de lobjet auquel il sert dintermdiaire. Il accomplit sa tche en transmettant lorsquil se doit les requtes lobjet sous-jacent auquel il contrle laccs. Un exemple classique du pattern PROXY intervient pour rendre plus transparent le chargement dimages volumineuses en mmoire. Imaginez que les images dune application doivent apparatre dans des pages ou panneaux qui ne sont pas afchs initialement. Pour viter de charger ces images avant quelles ne soient requises, vous pourriez leur substituer des proxies qui soccuperaient de les charger la demande. Cette section prsente un exemple dun tel proxy. Notez toutefois que les conceptions qui emploient le pattern PROXY sont parfois fragiles car elles sappuient sur la transmission dappels de mthodes des objets sous-jacents. Cette transmission peut produire une conception fragile et coteuse en maintenance.

116

Partie II

Patterns de responsabilit

Imaginez que vous soyez ingnieur chez Oozinoz et travailliez un proxy dimage qui, pour des raisons de performances, afchera une petite image temporaire pendant le chargement dune image plus volumineuse. Vous disposez dun prototype oprationnel (voir Figure 11.1). Le code de cette application est contenu dans la classe ShowProxy du package app.proxy. Le code sous-jacent qui supporte cette application se trouve dans le package com.oozinoz.imaging.

Figure 11.1
Les captures dcran illustrent une mini-application avant, pendant et aprs le chargement dune image volumineuse (cette image appartient au domaine public. Library of Congress, Prints and Photographs Division, Gottscho-Schleisner Collection [LC-G605-CT-00488]).

Linterface utilisateur afche lune des trois images suivantes : une image indiquant que le chargement na pas encore commenc (Absent), une image indiquant que limage est en cours de chargement (Loading), ou limage voulue. Lorsque lapplication dmarre, elle afche Absent, une image JPEG cre laide dun outil de dessin. Lorsque lutilisateur clique sur Load, une image Loading prdnie safche presque instantanment. Aprs quelques instants, limage dsire apparat.

Chapitre 11

PROXY

117

Un moyen ais dafcher une image enregistre au format JPEG, par exemple, est dutiliser un objet ImageIcon comme argument dun "label" qui afchera limage :
ImageIcon icon = new ImageIcon("images/fest.jpg"); JLabel label = new JLabel(icon);

Dans lapplication que vous dveloppez, vous voulez passer JLabel un proxy qui transmettra les requtes de dessin de lcran (paint) : (1) une image Absent, (2) une image Loading, ou (3) limage dsire. Le ux des messages est reprsent dans le diagramme de squence de la Figure 11.2.
Figure 11.2
Un objet ImageIconProxy transmet les requtes paint() lobjet ImageIcon courant.
:Client :ImageIconProxy

:JLabel

current:ImageIcon

paint() paint() paint()

Lorsque lutilisateur clique sur Load, votre code fait en sorte que limage courante de lobjet ImageIconProxy devienne Loading, et le proxy entame le chargement de limage attendue. Une fois celle-ci compltement charge, elle devient limage courante de ImageIconProxy. Pour crer un proxy, vous pouvez driver une sous-classe de ImageIcon, comme le montre la Figure 11.3. Le code de ImageIconProxy dnit deux variables statiques contenant les images Absent et Loading :
static final ImageIcon ABSENT = new ImageIcon( ClassLoader.getSystemResource("images/absent.jpg")); static final ImageIcon LOADING = new ImageIcon( ClassLoader.getSystemResource("images/loading.jpg"));

118

Partie II

Patterns de responsabilit

Excutable

ImageIcon //~25 champs non inclus ici getIconHeight():int getIconWidth():int paintIcon( c:Component, g:Graphics, x:int, y:int) //~50 autres mthodes

ImageIconProxy ABSENT:ImageIcon LOADING:ImageIcon current:ImageIcon ImageIconProxy( filename:String) load(callback:JFrame) run() getIconHeight():int getIconWidth():int paintIcon()

Figure 11.3
Un objet ImageIconProxy peut remplacer un objet ImageIcon puisquil sagit en fait dun objet ImageIcon.

Le constructeur de ImageIconProxy reoit le nom dun chier dimage charger. Lorsque la mthode load() dun objet ImageIconProxy est invoque, elle dnit limage comme tant LOADING et lance un thread spar pour charger limage. Le fait demployer un thread spar vite lapplication de devoir patienter pendant le chargement. La mthode load() reoit un objet JFrame qui est rappel par la mthode run() lissue du chargement. Voici le code presque complet de ImageIconProxy.java:
package com.oozinoz.imaging; import java.awt.*; import javax.swing.*; public class ImageIconProxy extends ImageIcon implements Runnable { static final ImageIcon ABSENT = new ImageIcon( ClassLoader.getSystemResource("images/absent.jpg")); static final ImageIcon LOADING = new ImageIcon( ClassLoader.getSystemResource("images/loading.jpg")); ImageIcon current = ABSENT; protected String filename; protected JFrame callbackFrame; public ImageIconProxy(String filename) {

Chapitre 11

PROXY

119

super(ABSENT.getImage()); this.filename = filename; } public void load(JFrame callbackFrame) { this.callbackFrame = callbackFrame; current = LOADING; callbackFrame.repaint(); new Thread(this).start(); } public void run() { current = new ImageIcon( ClassLoader.getSystemResource(filename)); callbackFrame.pack(); } public int getIconHeight() { /* Exercice ! */ } public int getIconWidth() { /* Exercice ! */ } public synchronized void paintIcon( Component c, Graphics g, int x, int y) { // Exercice ! } }

Exercice 11.1 Un objet ImageIconProxy accepte trois appels dafchage dimage quil doit passer limage courante. Ecrivez le code des mthodes getIconHeight(), getIconWidth() et paintIcon() de la classe ImageIconProxy.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.


Imaginez que vous parveniez faire fonctionner le code de cette petite application de dmonstration. Avant de crer la vritable application, laquelle ne se limite pas un bouton Load, vous procdez une rvision de la conception, qui rvle toute sa fragilit. Exercice 11.2 La classe ImageIconProxy ne constitue pas un composant rutilisable bien conu. Citez deux problmes de cette conception.

120

Partie II

Patterns de responsabilit

Lorsque vous rvisez la conception dun dveloppeur, vous devez la fois comprendre cette conception et vous former une opinion son sujet. Il se peut que le dveloppeur pense avoir utilis un pattern spcique alors que vous doutez quil soit prsent. Dans lexemple prcdent, le pattern PROXY apparat de manire vidente mais ne garantit en rien que la conception soit bonne. Il existe dailleurs de bien meilleures conceptions. Lorsque ce pattern est prsent, il doit pouvoir tre justi car la transmission de requtes peut entraner des problmes que dautres conceptions permettraient dviter. La prochaine section devrait vous aider dterminer si le pattern PROXY est une option valable pour votre conception.

Reconsidration des proxies dimage


A ce stade, peut-tre vous demandez-vous si les patterns de conception vous ont t dune quelconque aide. Vous avez implment dlement un pattern et voil que vous cherchez maintenant vous en dbarrasser. Il sagit en fait dune tape naturelle et mme saine, qui survient plus souvent dans des conditions relles de dveloppement que dans les livres. En effet, un auteur peut, avec laide de ses relecteurs, repenser et remplacer une conception de qualit insufsante avant que son ouvrage ne soit publi. Dans la pratique, un pattern peut vous aider faire fonctionner une application et faciliter les discussions sur sa conception. Dans lexemple de ImageIconProxy, le pattern a servi cela, mme sil est beaucoup plus simple dobtenir leffet dsir sans implmenter littralement un proxy. La classe ImageIcon opre sur un objet Image. Plutt que de transmettre les requtes de dessin de lcran un objet ImageIcon spar, il est plus facile doprer sur lobjet Image envelopp dans ImageIcon. La Figure 11.4 prsente une classe LoadingImageIcon (tire du package com.oozinoz.imaging) qui possde seulement deux mthodes, load() et run(), en plus de ses constructeurs.
Figure 11.4
La classe LoadingImageIcon fonctionne en changeant lobjet Image quelle contient.
ImageIcon image:Image getImage():Image setImage(i:Image) LoadingImageIcon ABSENT:ImageIcon LOADING:ImageIcon LoadingImageIcon( filename:String) load(callback:JFrame) run()

Chapitre 11

PROXY

121

La mthode load() de cette classe rvise reoit toujours un objet JFrame rappeler aprs le chargement de limage souhaite. Lorsquelle sexcute, elle invoque setImage() avec limage de LOADING, redessine le cadre (frame) et lance un thread spar pour elle-mme. La mthode run(), qui sexcute dans un thread spar, cre un nouvel objet ImageIcon pour le chier nomm dans le constructeur, appelle setImage() avec limage de cet objet et redessine le cadre. Voici le code presque complet de LoadingImageIcon.java:
package com.oozinoz.imaging; import javax.swing.ImageIcon; import javax.swing.JFrame; public class LoadingImageIcon extends ImageIcon implements Runnable { static final ImageIcon ABSENT = new ImageIcon( ClassLoader.getSystemResource("images/absent.jpg")); static final ImageIcon LOADING = new ImageIcon( ClassLoader.getSystemResource("images/loading.jpg")); protected String filename; protected JFrame callbackFrame; public LoadingImageIcon(String filename) { super(ABSENT.getImage()); this.filename = filename; } public void load(JFrame callbackFrame) { // Exercice ! } public void run() { // Exercice ! } }

Exercice 11.3 Ecrivez le code des mthodes load() et run() de LoadingImageIcon.

Ce code rvis est moins li la conception de ImageIcon, sappuyant principalement sur getImage() et setImage() et non sur la transmission dappels. En fait, il ny a pas du tout de transmission. LoadingImageIcon a seulement lapparence dun proxy, et non la structure.

122

Partie II

Patterns de responsabilit

Le fait que le pattern PROXY ait recours la transmission peut accrotre la maintenance du code. Par exemple, si lobjet sous-jacent change, lquipe dOozinoz devra actualiser le proxy. Pour viter cela, vous devriez lorsque vous le pouvez renoncer ce pattern. Il existe cependant des situations o vous navez dautre choix que de lutiliser. En particulier, lorsque lobjet pour lequel vous devez intercepter des messages sexcute sur une autre machine, ce pattern est parfois la seule option envisageable.

Proxy distant
Lorsque vous voulez invoquer une mthode dun objet qui sexcute sur un autre ordinateur, vous ne pouvez le faire directement et devez donc trouver un autre moyen de communiquer avec lui. Vous pourriez ouvrir un socket sur lhte distant et laborer un protocole pour envoyer des messages lobjet. Idalement, une telle approche vous permettrait de lui passer des messages de la mme manire que sil tait local. Vous devriez pouvoir appeler les mthodes dun objet proxy qui transmettrait ces requtes lobjet distant. En fait, de telles conceptions ont dj t implmentes, notamment dans CORBA (Common Object Request Broker Architecture), dans ASP.NET (Active Server Pages for .NET), et dans Java RMI (Remote Method Invocation). Grce RMI, un client peut assez aisment obtenir un objet proxy qui transmette les appels vers lobjet dsir actif sur une autre machine. Il importe de connatre RMI puisque ce mcanisme fait partie des fondements de la spcication EJB (Enterprise JavaBeans), un standard important de lindustrie. Indpendamment de la faon dont les standards de lindustrie voluent, le pattern PROXY continuera de jouer un rle important dans les environnements distribus, du moins dans un avenir proche, et RMI reprsente un bon exemple dimplmentation de ce pattern. Pour vous familiariser avec RMI, vous aurez besoin dun ouvrage de rfrence sur le sujet, tel que JavaTM Enterprise in a Nutshel (Java en concentr : Manuel de rfrence pour Java) [Flanagan et al. 2002]. Lexemple prsent dans cette section nest pas un tutoriel sur RMI mais permet de mettre en vidence la prsence et limportance du pattern PROXY dans les applications RMI. Nous laisserons de ct les difcults de conception introduites par RMI et EJB. Supposez que vous ayez dcid dexplorer le fonctionnement de RMI en rendant les mthodes dun objet accessibles un programme Java qui sexcute sur un autre

Chapitre 11

PROXY

123

ordinateur. La premire tape de dveloppement consiste crer une interface pour la classe qui doit tre accessible distance. Vous commencez par crer une interface Rocket qui est indpendante du code existant Oozinoz :
package com.oozinoz.remote; import java.rmi.*; public interface Rocket extends Remote { void boost(double factor) throws RemoteException; double getApogee() throws RemoteException; double getPrice() throws RemoteException; }

Linterface Rocket tend Remote et ses mthodes dclarent toutes quelles gnrent (throw) des exceptions distantes (RemoteException). Expliquer cet aspect de linterface dpasse le cadre du prsent livre, mais nimporte quel ouvrage didacticiel sur RMI devrait le faire. Votre rfrence RMI devrait galement expliquer que, pour agir en tant que serveur, limplmentation de votre interface distante peut tendre UnicastRemoteObject, comme illustr Figure 11.5.
Figure 11.5
Pour utiliser RMI, vous pouvez dabord dnir linterface souhaite pour les messages changs entre les deux ordinateurs puis crer une sous-classe de UnicastRemoteObject qui implmente cette interface.
interface Rocket boost(factor:double) getApogee():double getPrice():double UnicastRemoteObject

RocketImpl apogee:double price:double RocketImpl(price:double,apogee:double) boost(factor:double) getApogee():double getPrice():double

124

Partie II

Patterns de responsabilit

Vous avez prvu que des objets RocketImpl soient actifs sur un serveur et accessibles via un proxy qui lui est actif sur un client. Le code de la classe RocketImpl est simple :
package com.oozinoz.remote; import java.rmi.*; import java.rmi.server.UnicastRemoteObject; public class RocketImpl extends UnicastRemoteObject implements Rocket { protected double price; protected double apogee; public RocketImpl(double price, double apogee) throws RemoteException { this.price = price; this.apogee = apogee; } public void boost(double factor) { apogee *= factor; } public double getApogee() { return apogee; } public double getPrice() { return price; } }

Une instance de RocketImpl peut tre active sur une machine et accessible un programme Java excut sur une autre machine. Pour que cela puisse fonctionner, un client a besoin dun proxy pour lobjet RocketImpl. Ce proxy doit implmenter linterface Rocket et possder les fonctionnalits additionnelles requises pour communiquer avec un objet distant. Un gros avantage de RMI est quil automatise la construction de ce proxy. Pour gnrer le proxy, placez le chier RocketImpl.java et le chier dinterface Rocket.java sous le rpertoire dans lequel vous excuterez le registre RMI :
c:\rmi>dir /b com\oozinoz\remote RegisterRocket.class RegisterRocket.java Rocket.class

Chapitre 11

PROXY

125

Rocket.java RocketImpl.class RocketImpl.java ShowRocketClient.class ShowRocketClient.java

Pour crer la classe stub RocketImpl qui facilite la communication distante, excutez le compilateur RMI livr avec le JDK :
c:\rmi> rmic com.oozinoz.remote.RocketImpl

Notez que lexcutable rmic reoit comme argument un nom de classe, et non un nom de chier. Depuis la version 1.2 du JDK, le compilateur RMI cre un seul chier stub dont les machines client et serveur ont toutes deux besoin. Les versions antrieures craient des chiers spars pour une utilisation sur le client et le serveur. La commande rmic cre une classe RocketImpl_Stub:
c:\rmi>dir /b com\oozinoz\remote RegisterRocket.class RegisterRocket.java Rocket.class Rocket.java RocketImpl.class RocketImpl.java RocketImpl_Stub.class ShowRocketClient.class ShowRocketClient.java

Pour rendre un objet actif, il faut lenregistrer auprs dun registre RMI qui sexcute sur le serveur. Lexcutable rmiregistry est intgr au JDK. Lorsque vous excutez le registre, spciez le port sur lequel il coutera :
c:\rmi> rmiregistry 5000

Une fois le registre en cours dexcution sur le serveur, vous pouvez crer et enregistrer un objet RocketImpl:
package com.oozinoz.remote; import java.rmi.*; public class RegisterRocket { public static void main(String[] args) { try { // Exercice ! Naming.rebind( "rmi://localhost:5000/Biggie", biggie); System.out.println("biggie enregistr");

126

Partie II

Patterns de responsabilit

} catch (Exception e) { e.printStackTrace(); } } }

Si vous compilez et excutez ce code, le programme afchera une conrmation de lenregistrement de la fuse :
biggie enregistr

Vous devez remplacer la ligne // Exercice ! de la classe RegisterRocket par le code qui cre un objet biggie modlisant une fuse. Le reste du code de la mthode main() enregistre cet objet. Une description du fonctionnement de la classe Naming dpasse le cadre de cette discussion. Vous devriez nanmoins disposer des informations sufsantes pour pouvoir crer lobjet biggie que ce code enregistre. Exercice 11.4 Remplacez la ligne // Exercice ! par une dclaration et une instanciation de lobjet biggie. Dnissez cet objet de sorte quil modlise une fuse dont le prix est de 29,95 dollars et lapoge de 820 mtres.

Le fait dexcuter le programme RegisterRocket rend un objet RocketImpl, en loccurrence biggie, disponible sur un serveur. Un client qui sexcute sur une autre machine peut accder biggie sil dispose dun accs linterface Rocket et la classe RocketImpl_Stub. Si vous travaillez sur une seule machine, vous pouvez quand mme raliser ce test en accdant au serveur sur localhost plutt que sur un autre hte :
package com.oozinoz.remote; import java.rmi.*; public class ShowRocketClient { public static void main(String[] args) { try { Object obj = Naming.lookup( "rmi://localhost:5000/Biggie"); Rocket biggie = (Rocket) obj; System.out.println( "Lapoge est " + biggie.getApogee());

Chapitre 11

PROXY

127

} catch (Exception e) { System.out.println( "Exception lors de la recherche dune fuse :"); e.printStackTrace(); } } }

Lorsque ce programme sexcute, il recherche un objet enregistr sous le nom de "Biggie". La classe qui fournit ce nom est RocketImpl et lobjet obj retourn par lookup() sera une instance de la classe RocketImpl_Stub. Cette dernire implmente linterface Rocket, aussi est-il lgal de convertir (cast) lobjet obj en une instance de Rocket. La classe RocketImpl_Stub tend en fait une classe RemoteStub qui permet lobjet de communiquer avec un serveur. Lorsque vous excutez le programme ShowRocketClient, il afche lapoge dune fuse "Biggie" :
Lapoge est de 820.0

Par lintermdiaire dun proxy, lappel de getApogee() est transmis une implmentation de linterface Rocket qui est active sur un serveur. Exercice 11.5 La Figure 11.6 illustre lappel de getApogee() qui est transmis. Lobjet le plus droite apparat en gras pour signier quil est actif en dehors du programme ShowRocketClient. Compltez les noms de classes manquants.
Figure 11.6
Ce diagramme, une fois complt, reprsentera le ux de messages dans une application distribue base sur RMI.
ShowRocketClient :?

:?

getApogee() getApogee()

128

Partie II

Patterns de responsabilit

Lintrt de RMI est quil permet des programmes client dinteragir avec un objet local servant de proxy pour un objet distant. Vous dnissez linterface de lobjet que sont censs se partager le client et le serveur. RMI fournit, lui, le mcanisme de communication et dissimule au serveur et au client le fait que deux implmentations de Rocket collaborent pour assurer une communication interprocessus quasiment transparente.

Proxy dynamique
Les ingnieurs dOozinoz sont parfois confronts des problmes de performances et aimeraient trouver un moyen dinstrumentaliser le code sans avoir apporter de changements majeurs leur conception. Java offre une fonctionnalit qui peut les aider dans ce sens : le proxy dynamique. Un tel proxy permet denvelopper un objet dans un autre. Vous pouvez faire en sorte que lobjet extrieur le proxy intercepte tous les appels destins lobjet envelopp. Le proxy passe habituellement ces appels lobjet intrieur, mais vous pouvez ajouter du code qui sexcute avant ou aprs les appels intercepts. Certaines limitations de cette fonctionnalit vous empchent denvelopper nimporte quel objet. En revanche, dans des conditions adquates, vous disposez dun contrle total sur lopration accomplie par lobjet envelopp. Un proxy dynamique utilise les interfaces implmentes par la classe dun objet. Les appels pouvant tre intercepts par le proxy sont dnis dans lune de ces interfaces. Si vous disposez dune classe qui implmente une interface dont vous voulez intercepter certaines mthodes, vous pouvez utiliser un proxy dynamique pour envelopper une instance de cette classe. Pour crer un proxy dynamique, vous devez avoir la liste des interfaces intercepter. Heureusement, cette liste peut gnralement tre obtenue en interrogeant lobjet que vous voulez envelopper au moyen dune ligne de code comme la suivante :
Class[] classes = obj.getClass().getInterfaces();

Ce code indique que les mthodes intercepter appartiennent aux interfaces implmentes par la classe dun objet. La cration dun proxy dynamique ncessite deux autres ingrdients : un chargeur de classe (loader) et une classe contenant le comportement que vous voulez excuter lorsque votre proxy intercepte un appel.

Chapitre 11

PROXY

129

Comme pour la liste dinterfaces, vous pouvez obtenir un chargeur de classe appropri en utilisant celui associ lobjet envelopper :
ClassLoader loader = obj.getClass().getClassLoader();

Le dernier ingrdient requis est lobjet proxy lui-mme. Cet objet doit tre une instance dune classe qui implmente linterface InvocationHandler du package java.lang.reflect. Cette interface dclare lopration suivante :
public Object invoke(Object proxy, Method m, Object[] args) throws Throwable;

Lorsque vous enveloppez un objet dans un proxy dynamique, les appels destins cet objet sont drouts vers cette opration invoke(), dans une classe que vous fournissez. Le code de votre mthode invoke() devra probablement passer lobjet envelopp chaque appel de mthode. Vous pouvez passer linvocation avec une ligne comme celle-ci :
result = m.invoke(obj, args);

Cette ligne utilise le principe de rexion pour passer lappel dsir lobjet envelopp. Le grand intrt des proxies dynamiques est que vous pouvez ajouter nimporte quel comportement avant ou aprs lexcution de cette ligne. Imaginez que vous vouliez consigner un avertissement lorsquune mthode est longue sexcuter. Vous pourriez crer une classe ImpatientProxy avec le code suivant :
package app.proxy.dynamic; import java.lang.reflect.*; public class ImpatientProxy implements InvocationHandler { private Object obj; private ImpatientProxy(Object obj) { this.obj = obj; } public Object invoke( Object proxy, Method m, Object[] args) throws Throwable { Object result; long t1 = System.currentTimeMillis(); result = m.invoke(obj, args);

130

Partie II

Patterns de responsabilit

long t2 = System.currentTimeMillis(); if (t2 - t1 > 10) { System.out.println( "> Il faut " + (t2 - t1) + " millisecondes pour invoquer " + m.getName() + "() avec"); for (int i = 0; i < args.length; i++) System.out.println( "> arg[" + i + "]: " + args[i]); } return result; } }

Cette classe implmente la mthode invoke() de manire quelle vrie le temps que prend lobjet envelopp pour accomplir lopration invoque. Si la dure dexcution est trop longue, la classe ImpatientProxy afche un avertissement. Pour pouvoir utiliser un objet ImpatientProxy, vous devez employer la classe Proxy du package java.lang.reflect. Cette classe a besoin dune liste dinterfaces et dun chargeur de classe, ainsi que dune instance de ImpatientProxy. Pour simplier la cration du proxy dynamique, nous pourrions ajouter la mthode suivante la classe ImpatientProxy:
public static Object newInstance(Object obj) { ClassLoader loader = obj.getClass().getClassLoader(); Class[] classes = obj.getClass().getInterfaces(); return Proxy.newProxyInstance( loader, classes, new ImpatientProxy(obj)); }

La mthode statique newInstance() cre un proxy dynamique pour nous. A partir dun objet envelopper, elle extrait la liste des interfaces et le chargeur de classe de cet objet. Puis elle instancie la classe ImpatientProxy en lui passant lobjet envelopper. Tous ces ingrdients sont ensuite passs la mthode newProxyInstance() de la classe Proxy. Lobjet retourn implmente toutes les interfaces implmentes par la classe de lobjet envelopp. Nous pouvons convertir lobjet retourn en nimporte laquelle de ces interfaces. Imaginez que vous travailliez avec un ensemble (set) dobjets et que certaines oprations semblent sexcuter lentement. Pour dterminer quels objets sont

Chapitre 11

PROXY

131

concerns, vous pouvez envelopper lensemble dans un objet ImpatientProxy, comme illustr ci-aprs :
package app.proxy.dynamic; import import import import import java.util.HashSet; java.util.Set; com.oozinoz.firework.Firecracker; com.oozinoz.firework.Sparkler; com.oozinoz.utility.Dollars;

public class ShowDynamicProxy { public static void main(String[] args) { Set s = new HashSet(); s = (Set)ImpatientProxy.newInstance(s); s.add(new Sparkler( "Mr. Twinkle", new Dollars(0.05))); s.add(new BadApple("Lemon")); s.add(new Firecracker( "Mr. Boomy", new Dollars(0.25))); System.out.println( "Lensemble contient " + s.size() + " lments."); } }

Ce code cre un objet Set pour contenir quelques lments. Puis il enveloppe cet ensemble dans un objet ImpatientProxy, convertissant le rsultat de la mthode newInstance() en un objet Set. La consquence est que lobjet s se comporte comme un ensemble, sauf que le code de ImpatientProxy produira un avertissement si une des mthodes est trop longue sexcuter. Par exemple, lorsque le programme invoque la mthode add() de lensemble, notre objet ImpatientProxy intercepte lappel et le passe lensemble en minutant le rsultat de chaque appel. Lexcution du programme ShowDynamicProxy produit le rsultat suivant :
> Il faut 1204 millisecondes pour invoquer add() avec > arg[0]: Lemon Lensemble contient 3 lments.

Le code de ImpatientProxy nous aide identier lobjet qui est long ajouter lensemble. Il sagit de linstance Lemon de la classe BadApple. Voici le code de cette classe :
package app.proxy.dynamic; public class BadApple { public String name;

132

Partie II

Patterns de responsabilit

public BadApple(String name) { this.name = name; } public boolean equals(Object o) { if (!(o instanceof BadApple)) return false; BadApple f = (BadApple) o; return name.equals(f.name); } public int hashCode() { try { Thread.sleep(1200); } catch (InterruptedException ignored) { } return name.hashCode(); } public String toString() { return name; } }

Le code de ShowDynamicProxy utilise un objet ImpatientProxy pour surveiller les appels destins un ensemble. Il nexiste toutefois aucun lien entre un ensemble donn et ImpatientProxy. Aprs avoir crit une classe de proxy dynamique, vous pouvez lutiliser pour envelopper nimporte quel objet ds lors que celui-ci est une instance dune classe qui implmente une interface dclarant le comportement que vous voulez intercepter. La possibilit de crer un comportement pouvant tre excut avant ou aprs les appels intercepts est lune des ides de base de la programmation oriente aspect ou POA (AOP, Aspect-Oriented Programming). Un aspect combine les notions dadvice le code que vous voulez insrer et de point-cuts la dnition de points dexcution o vous voulez que le code insr soit excut. Des livres entiers sont consacrs la POA, mais vous pouvez avoir un avant-got de lapplication de comportements rutilisables une varit dobjets en utilisant des proxies dynamiques. Un proxy dynamique vous permet denvelopper un objet dans un proxy qui intercepte les appels destins cet objet et qui ajoute un comportement avant ou aprs le passage de ces appels lobjet envelopp. Vous pouvez ainsi crer des comportements rutilisables applicables nimporte quel objet, comme en programmation oriente aspect.

Chapitre 11

PROXY

133

Rsum
Les implmentations du pattern PROXY produisent un objet intermdiaire qui gre laccs un objet cible. Un objet proxy peut dissimuler aux clients les changements dtat dun objet cible, comme dans le cas dune image qui ncessite un certain temps pour se charger. Le problme est que ce pattern sappuie habituellement sur un couplage troit entre lintermdiaire et lobjet cible. Dans certains cas, la solution consiste utiliser un proxy dynamique. Lorsque la classe dun objet implmente des interfaces pour les mthodes que vous voulez intercepter, vous pouvez envelopper lobjet dans un proxy dynamique et faire en sorte que votre code sexcute avant/aprs le code de lobjet envelopp ou sa place.

12
CHAIN OF RESPONSABILITY
Les dveloppeurs sefforcent dassocier les objets de manire souple avec une responsabilit minimale et spcique entre objets. Cette pratique permet de procder plus facilement des changements et avec moins de risques dintroduire des dfauts. Dans une certaine mesure, la dissociation se produit naturellement en Java. Les clients ne voient que linterface visible dun objet et sont affranchis des dtails de son implmentation. Cette organisation laisse toutefois en place lassociation fondamentale pour que le client sache quel objet possde la mthode quil doit appeler. Vous pouvez assouplir la restriction forant un client savoir quel objet utiliser lorsque vous pouvez organiser un groupe dobjets sous forme dune sorte de hirarchie qui permet chaque objet soit de raliser une opration, soit de passer la requte un autre objet. Lobjectif du pattern CHAIN OF RESPONSABILITY est dviter de coupler lmetteur dune requte son rcepteur en permettant plus dun objet dy rpondre.

Une chane de responsabilits ordinaire


Le pattern CHAIN OF RESPONSABILITY apparat souvent dans notre quotidien rel lorsquune personne responsable dune tche sen acquitte personnellement ou la dlgue quelquun dautre. Cette situation se produit chez Oozinoz avec des ingnieurs responsables de la maintenance des machines de fabrication de fuses. Comme dcrit au Chapitre 5, Oozinoz modlise des machines, des lignes de montage, des traves, et des units de production (ou usine) en tant que composants matriels de fabrication (objet MachineComponent). Cette approche permet limplmentation

136

Partie II

Patterns de responsabilit

simple et rcursive doprations telles que larrt de toutes les machines dune trave. Elle simplie aussi la modlisation des responsabilits de fabrication au sein de lusine. Chez Oozinoz, il y a toujours un ingnieur responsable pour nimporte quel composant matriel, bien que cette responsabilit puisse tre assigne diffrents niveaux. Par exemple, il peut y avoir un ingnieur directement assign la maintenance dune machine complexe mais pas forcment dans le cas dune machine simple. Dans ce dernier cas, cest lingnieur responsable de la ligne ou de la trave laquelle participe la machine qui en assumera la responsabilit. Nous voudrions ne pas forcer les objets clients interroger plusieurs objets lorsquils recherchent lingnieur responsable. Nous pouvons ici appliquer le pattern CHAIN OF RESPONSABILITY, en associant chaque composant matriel un objet responsible. La Figure 12.1 illustre cette conception.
Figure 12.1
Chaque objet
MachineComponent parent:MachineComponent responsible:Engineer getParent():MachineComponent getResponsible():Engineer

Machine MachineComposite possde


un parent et une association de responsabilit, hrits de la classe

MachineComponent.

Machine

MachineComposite

La conception illustre Figure 12.1 permet, sans quon ait le requrir, que chaque composant matriel garde trace de son ingnieur responsable. Si une machine na pas dingnieur ddi, elle peut passer une requte demandant son ingnieur responsable dtre son "parent". Dans la pratique, le parent dune machine est une ligne, celui dune ligne est une trave, et celui dune trave est une unit de production. Chez Oozinoz, il y a toujours un ingnieur responsable quelque part dans cette chane.

Chapitre 12

CHAIN OF RESPONSABILITY

137

Lavantage de cette conception est que les clients de composants matriels nont pas besoin de dterminer comment les ingnieurs sont attribus. Un client peut demander nimporte quel composant son ingnieur responsable. Les composants vitent aux clients davoir connatre la faon dont les responsabilits sont distribues. Dun autre ct, cette conception prsente quelques ventuels inconvnients. Exercice 12.1 Citez deux faiblesses de la conception illustre Figure 12.1.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

Le pattern CHAIN OF RESPONSABILITY permet de simplier le code du client lorsquil nest pas vident de savoir quel objet dun groupe dobjets doit traiter une requte. Si ce pattern ntait pas dj implment, vous pourriez remarquer certaines situations o il pourrait vous aider migrer votre code vers une conception plus simple.

Refactorisation pour appliquer CHAIN OF RESPONSABILITY


Si vous remarquez quun code client effectue des appels de test avant dmettre la requte effective, vous pourriez lamliorer au moyen dune refactorisation. Pour appliquer le pattern CHAIN OF RESPONSABILITY, dterminez lopration que les objets dun groupe de classes seront parfois en mesure de supporter. Par exemple, les composants matriels chez Oozinoz peuvent parfois fournir une rfrence dingnieur responsable. Ajoutez lopration souhaite chaque classe dans le groupe, mais implmentez lopration au moyen dune stratgie de chanage pour les cas o un objet spcique ncessiterait de laide pour rpondre la requte. Considrez le code Oozinoz de modlisation doutils (Tool) et de chariots doutils (Tool Cart). Les outils ne font pas partie de la hirarchie MachineComponent mais ils partagent quelques similitudes avec ces composants. Plus prcisment, les outils sont toujours assigns aux chariots doutils, et ces derniers ont un ingnieur responsable. Imaginez un afchage pouvant montrer tous les outils et les machines dune certaine trave et disposant dune aide afchant lingnieur responsable pour

138

Partie II

Patterns de responsabilit

nimporte quel lment choisi. La Figure 12.2 illustre les classes impliques dans lidentication de lingnieur responsable dun quipement slectionn.

interface VisualizationItem

...

Tool

MachineComponent

ToolCart responsible:Engineer

getParent():MachineComponent getResponsible():Engineer getResponsible():Engineer

Machine

MachineComposite

Figure 12.2
Les lments dune simulation comprennent des machines, des machines composites, des outils et des chariots doutils.

Linterface VisualizationItem spcie quelques comportements que les classes requirent pour participer lafchage, mais ne possde pas de mthode getResponsible(). En fait, tous les lments de la visualisation nont pas une connaissance directe de leur responsable. Lorsque la visualisation doit dterminer lingnieur responsable dun lment, la rponse dpend du type de llment slectionn. Les machines, les groupes de machines et les chariots doutils disposent dune mthode getResponsible(), mais pas les outils. Pour ceux-ci, le code doit

Chapitre 12

CHAIN OF RESPONSABILITY

139

identier le chariot auquel appartient loutil et dterminer le responsable du chariot. Pour trouver lingnieur responsable dun lment simul, un code de menu dapplication utilise une srie dinstructions if et de tests du type dlment. Cela est le signe quune refactorisation pourrait amliorer le code qui se prsente comme suit :
package com.oozinoz.machine; public class AmbitiousMenu { public Engineer getResponsible(VisualizationItem item) { if (item instanceof Tool) { Tool t = (Tool) item; return t.getToolCart().getResponsible(); } if (item instanceof ToolCart) { ToolCart tc = (ToolCart) item; return tc.getResponsible(); } if (item instanceof MachineComponent) { MachineComponent c = (MachineComponent) item; if (c.getResponsible() != null) return c.getResponsible(); if (c.getParent() != null) return c.getParent().getResponsible(); } return null; } }

Lobjectif de CHAIN OF RESPONSABILITY est dexonrer le code appelant de lobligation de savoir quel objet peut traiter une requte. Dans cet exemple, lappelant est un menu et la requte concerne lidentication dun ingnieur responsable. Dans la conception actuelle, lappelant doit connatre les lments qui possdent une mthode getResponsible(). Vous pouvez perfectionner ce code en appliquant CHAIN OF RESPONSABILITY, en donnant tous les lments simuls un tiers responsable. Ainsi, ce sont les objets simuls qui ont pour charge de connatre leur responsable et non plus le menu. Exercice 12.2 Redessinez le diagramme de la Figure 12.2 en dplaant la mthode getResponsible() vers VisualizationItem et en ajoutant ce comportement Tool.

140

Partie II

Patterns de responsabilit

Le code de menu devient plus simple maintenant quil peut demander chaque lment pouvant tre slectionn son ingnieur responsable :
package com.oozinoz.machine; public class AmbitiousMenu2 { public Engineer getResponsible(VisualizationItem item) { return item.getResponsible(); } }

Limplmentation de la mthode getResponsible() pour chaque lment est galement simple. Exercice 12.3 Ecrivez le code de la mthode getResponsible() pour : A. MachineComponent B. Tool C. ToolCart

Ancrage dune chane de responsabilits


Lorsque vous crivez la mthode getResponsible() pour MachineComponent, vous devez considrer le fait que le parent dun objet MachineComponent puisse tre null. Une autre solution est dtre un peu plus strict dans votre modle objet et dexiger que les objets MachineComponent aient un parent non null. Pour cela, vous pouvez ajouter un argument parent au constructeur de MachineComponent. Vous pouvez mme mettre une exception lorsque lobjet fourni est null, tant que vous savez o cette exception est intercepte. Considrez aussi le fait quun objet formera la racine (root) un objet particulier qui naura pas de parent. Une approche raisonnable est de crer une classe MachineRoot en tant que sous-classe de MachineComposite (pas de MachineComponent). Vous pouvez alors garantir quun objet MachineComponent aura toujours un ingnieur responsable si :
m m

Le constructeur (ou les constructeurs) de MachineRoot requiert un objet Engineer. Le constructeur (ou les constructeurs) de MachineComponent requiert un objet parent qui soit lui-mme un MachineComponent.

Chapitre 12

CHAIN OF RESPONSABILITY

141

Seul MachineRoot utilise null comme valeur pour son parent.


MachineComponent parent:MachineComponent responsible:Engineer

Figure 12.3
Comment les constructeurs peuvent-ils garantir que chaque objet MachineComponent aura un ingnieur responsable ?

Machine

MachineComposite

MachineRoot

Exercice 12.4 Compltez les constructeurs de la Figure 12.3 pour supporter une conception garantissant que chaque objet MachineComponent aura un ingnieur responsable.

En ancrant une chane de responsabilits, vous renforcez le modle objet et simpliez le code. Vous pouvez maintenant implmenter la mthode getResponsible() de MachineComponent comme suit :
public Engineer getResponsible() { if (responsible != null) return responsible; return parent.getResponsible(); }

142

Partie II

Patterns de responsabilit

CHAIN OF RESPONSABILITY sans COMPOSITE


Le pattern CHAIN OF RESPONSABILITY requiert une stratgie pour ordonner la recherche dun objet pouvant traiter une requte. Gnralement, lordre suivre dpendra dun aspect sous-jacent du domaine modlis. Cela se produit souvent lorsquil y a une sorte de composition, comme dans la hirarchie de composants matriels dOozinoz. Ce pattern peut toutefois sappliquer dautres modles que les modles composites. Exercice 12.5 Donnez un exemple dans lequel le pattern CHAIN OF RESPONSABILITY peut intervenir alors que les objets chans ne forment pas un composite.

Rsum
Lorsque vous appliquez le pattern CHAIN OF RESPONSABILITY, vous dispensez un client de devoir savoir quel objet dun ensemble supporte un certain comportement. En permettant laction de recherche de responsabilit de se produire le long de la chane dobjets, vous dissociez le client de tout objet spcique de la chane. Ce pattern intervient occasionnellement lorsquune chane dobjets arbitraire peut appliquer une srie de stratgies diverses pour rpondre un certain problme, tel que lanalyse dune entre utilisateur. Plus frquemment, il intervient dans le cas dagrgats, o une hirarchie disolement fournit un ordre naturel pour une chane dobjets. Ce pattern rsulte en un code plus simple au niveau la fois de la hirarchie et du client

13
FLYWEIGHT
Le pattern FLYWEIGHT permet le partage dun objet entre plusieurs clients, crant une responsabilit pour lobjet partag dont les objets ordinaires nont normalement pas se soucier. La plupart du temps, un seul client la fois dtient une rfrence vers un objet. Lorsque ltat de lobjet change, cest parce que le client la modi et lobjet na pas la responsabilit den informer les autres clients. Il est cependant parfois utile de pouvoir partager laccs un objet. Une raison de vouloir cela apparat lorsque vous devez grer des milliers ou des dizaines de milliers de petits objets, tels que les caractres dune version en ligne dun livre. Dans un tel cas, ce sera pour amliorer les performances an de pouvoir partager efcacement des objets dune grande granularit entre de nombreux clients. Un livre na besoin que dun objet A, bien quil ncessite un moyen de modliser les endroits o diffrents A apparaissent. Lobjectif du pattern FLYWEIGHT est dutiliser le partage pour supporter efcacement un grand nombre dobjets forte granularit.

Immuabilit
Le pattern FLYWEIGHT laisse plusieurs clients se partager un grand nombre de petits objets : les yweights (poids mouche). Pour que cela fonctionne, vous devez considrer que lorsquun client change ltat dun objet, cet tat est modi pour chaque client ayant accs lobjet. La faon la plus simple et la plus courante dviter quils se perturbent mutuellement est de les empcher dintroduire des changements dtat dans lobjet partag. Un moyen dy parvenir est de crer un objet qui soit immuable pour que, une fois cr, il ne puisse tre chang. Les objets immuables

144

Partie II

Patterns de responsabilit

les plus frquemment rencontrs dans Java sont des instances de la classe String. Une fois que vous avez cr une chane, ni vous ni aucun client pouvant y accder ne pourra changer ses caractres. Exercice 13.1 Donnez une justication du choix des crateurs de Java davoir rendu les objets String immuables, ou argumentez contre cette dcision si vous la jugez draisonnable.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

Lorsque vous avez un grand nombre dobjets similaires, vous pouvez vouloir en partager laccs, mais ils ne sont pas ncessairement immuables. Dans ce cas, une tape pralable lapplication de FLYWEIGHT est dextraire la partie immuable dun objet pour quelle puisse tre partage.

Extraction de la partie immuable dun yweight


Chez Oozinoz, les substances chimiques sont aussi rpandues que des caractres dans un document. Les services achat, ingnierie, fabrication et scurit sont tous impliqus dans la gestion de la circulation de milliers de substances chimiques dans lusine. Les prparations chimiques sont souvent modlises au moyen dinstances de la classe Substance illustre Figure 13.1. La classe Substance possde de meilleures mthodes pour ses attributs ainsi quune mthode getMoles() qui retourne le nombre de moles un compte de molcules dans une substance. Un objet Substance reprsente une certaine quantit dune certaine molcule. Oozinoz utilise une classe Mixture pour modliser des combinaisons de substances. Par exemple, la Figure 13.2 prsente un diagramme dune prparation de poudre noire. Supposez que, tant donn la prolifration de substances chimiques chez Oozinoz, vous dcidiez dappliquer le pattern FLYWEIGHT pour rduire le nombre dobjets Substance dans les applications. Pour traiter les objets Substance en tant que yweights, une premire tape est de sparer les parties immuables des parties variables. Supposez que vous dcidiez de restructurer la classe Substance en extrayant sa partie immuable pour la placer dans une classe Chemical.

Chapitre 13

FLYWEIGHT

145

Figure 13.1
Un objet Substance modlise une prparation chimique.
Substance name:String symbol:String atomicWeight:double grams:double getName():String getSymbol():String getAtomicWeight():double getGrams():double getMoles():double

Figure 13.2
Une prparation de poudre noire contient du salptre, du soufre et du charbon.
blackPowder:Mixture :Substance name = " Sulfur " :Substance name = " Saltpeter " symbol = " KNO3 " atomicWeight = 101 grams = 75 :Substance name = " Carbon " symbol = " C " atomicWeight = 12 grams = 15 symbol = S atomicWeight = 32 grams = 10

Exercice 13.2 Compltez le diagramme de classes de la Figure 13.3 pour prsenter une classe Substance2 restructure et une nouvelle classe Chemical immuable.

146

Partie II

Patterns de responsabilit

Figure 13.3
Compltez ce diagramme pour extraire les caractristiques immuables de Substance2 et les placer dans la classe Chemical.
?? ... ?? ...

Substance2 ?? ... ?? ...

Chemical

Partage des objets yweight


Extraire la partie immuable dun objet nest quune partie du travail dans lapplication du pattern FLYWEIGHT. Vous devez encore crer une classe factory yweight qui instancie les yweights, et faire en sorte que les clients se les partagent. Vous devez aussi vous assurer que les clients utiliseront votre factory au lieu de construire euxmmes des instances de la classe yweight. Pour crer des yweights, vous avez besoin dune factory, peut-tre une classe ChemicalFactory avec une mthode statique qui retourne une substance chimique daprs un nom donn. Vous pourriez stocker les substances dans une table de hachage, crant des substances connues lors de linitialisation de la factory. La Figure 13.4 illustre un exemple de conception pour ChemicalFactory.
Figure 13.4
La classe ChemicalFactory est une factory yweight qui retourne des objets Chemical.
ChemicalFactory -chemicals:Hashtable +getChemical(name:String):Chemical

Chemical

Le code de ChemicalFactory peut utiliser un initialisateur statique pour stocker les objets Chemical dans une table Hasthtable:
package com.oozinoz.chemical; import java.util.*;

Chapitre 13

FLYWEIGHT

147

public class ChemicalFactory { private static Map chemicals = new HashMap(); static { chemicals.put( "carbon", new Chemical("Carbon", "C", 12)); chemicals.put( "sulfur", new Chemical("Sulfur", "S", 32)); chemicals.put( "saltpeter", new Chemical("Saltpeter", "KN03", 101)); //... } public static Chemical getChemical(String name) { return (Chemical) chemicals.get(name.toLowerCase()); } }

Aprs avoir cr une factory pour les substances chimiques, vous devez maintenant prendre des mesures pour vous assurer que dautres dveloppeurs lutiliseront et ninstancieront pas eux-mmes la classe Chemical. Une approche simple est de sappuyer sur laccessibilit de la classe Chemical. Exercice 13.3 Comment pouvez-vous utiliser laccessibilit de la classe Chemical pour dcourager dautres dveloppeurs de linstancier ?

Les modicateurs daccs ne fournissent pas le contrle total sur linstanciation dont vous auriez besoin. Vous pourriez vous assurer que ChemicalFactory soit la seule classe pouvoir crer de nouvelles instances Chemical. Pour atteindre ce niveau de contrle, vous pouvez appliquer une classe interne en dnissant la classe Chemical dans ChemicalFactory (voir le package com.oozinoz.chemical2). Pour accder un type imbriqu, les clients doivent spcier le type "contenant", avec des expressions telles que les suivantes :
ChemicalFactory.Chemical c = ChemicalFactory.getChemical("saltpeter");

148

Partie II

Patterns de responsabilit

Vous pouvez simplier lemploi dune classe imbrique en faisant de Chemical une interface et en nommant la classe ChemicalImpl. Linterface Chemical peut spcier trois mthodes accesseurs, comme suit :
package com.oozinoz.chemical2; public interface Chemical { String getName(); String getSymbol(); double getAtomicWeight(); }

Les clients ne rfrenceront jamais directement la classe interne. Vous pouvez donc la dnir prive pour avoir la garantie que seul ChemicalFactory2 y aura accs. Exercice 13.4 Compltez le code suivant pour ChemicalFactory2.java.

package com.oozinoz.chemical2; import java.util.*; public class ChemicalFactory2 { private static Map chemicals = new HashMap(); /* Exercice ! */ implements Chemical { private String name; private String symbol; private double atomicWeight; ChemicalImpl( String name, String symbol, double atomicWeight) { this.name = name; this.symbol = symbol; this.atomicWeight = atomicWeight; } public String getName() { return name; } public String getSymbol() { return symbol; }

Chapitre 13

FLYWEIGHT

149

public double getAtomicWeight() { return atomicWeight; } public String toString() { return name + "(" + symbol + ")[" + atomicWeight + "]"; } } /* Exercice ! */ { chemicals.put("carbon", factory.new ChemicalImpl("Carbon", "C", 12)); chemicals.put("sulfur", factory.new ChemicalImpl("Sulfur", "S", 32)); chemicals.put("saltpeter", factory.new ChemicalImpl( "Saltpeter", "KN03", 101)); //... } public static Chemical getChemical(String name) { return /* Exercice ! */ } }

Rsum
Le pattern FLYWEIGHT vous permet de partager laccs des objets qui peuvent se prsenter en grande quantit, tels que des caractres ou des substances chimiques. Les objets yweight doivent tre immuables, une proprit que vous pouvez tablir en extrayant la partie immuable de la classe que vous voulez partager. Pour garantir le partage des objets yweight, vous pouvez fournir une classe factory partir de laquelle les clients pourront obtenir des yweights, puis forcer lemploi de cette factory. Les modicateurs daccs vous donnent un certain contrle sur laccs votre code par les autres dveloppeurs, mais vous bncierez dun meilleur contrle au moyen de classes internes en garantissant quune classe ne pourra tre accessible que par la classe qui la contient. En vous assurant que les clients utiliseront comme il se doit votre factory yweight, vous pouvez fournir un accs partag scuris de nombreux objets.

III
Patterns de construction

14
Introduction la construction
Lorsque vous crez une classe Java, vous prvoyez normalement une fonctionnalit pour la cration des objets en fournissant un constructeur. Un constructeur est cependant utile uniquement si les clients savent quelle classe instancier et disposent des paramtres que le constructeur attend. Plusieurs patterns de conception peuvent intervenir dans les situations o ces conditions, ou dautres circonstances de construction ordinaire, ne valent pas. Avant dexaminer ces types de conception utiles o la construction ordinaire ne suft pas, il peut tre utile de revoir ce quest une construction classique en Java.

Quelques ds de construction
Les constructeurs sont des mthodes spciales. Par bien des aspects, dont les modicateurs daccs, la surcharge ou les listes de paramtres, les constructeurs sapparentent des mthodes ordinaires. Dun autre ct, leur emploi et leur comportement sont rgis par un nombre signicatif de rgles syntaxiques et smantiques. Exercice 14.1 Citez quatre rgles gouvernant lusage et le comportement des constructeurs dans le langage Java.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

154

Partie III

Patterns de construction

Dans certains cas, Java fournit des constructeurs avec un comportement par dfaut. Tout dabord, si une classe ne possde pas de constructeur dclar, Java en fournit un par dfaut, lequel quivaut un constructeur public, nattendant aucun argument et ne comportant aucune instruction dans son corps. Un second comportement par dfaut du langage se produit lorsque la dclaration du constructeur dune classe nutilise pas une variation de this() ou de super() pour invoquer de faon explicite un autre constructeur. Java insre alors super() sans argument. Cela peut provoquer des rsultats surprenants, comme avec la compilation du code suivant :
package app.construction; public class Fuse { private String name; // public Fuse(String name) { this.name = name; } }

et :
package app.construction; public class QuickFuse extends Fuse { }

Ce code compile correctement tant que vous ne retirez pas les marques de commentaire //. Exercice 14.2 Expliquez lerreur qui se produira si vous retirez les marques de commentaire, permettant ainsi la super-classe Fuse daccepter un nom dans son constructeur.

La faon la plus courante dinstancier des objets est dinvoquer loprateur new, mais vous pouvez aussi utiliser la rexion. La rexion donne la possibilit de travailler avec des types et des membres de type en tant quobjets. Mme si vous nutilisez pas frquemment la rexion, il nest pas trop difcile de suivre la logique dun programme sappuyant sur cette technique, comme dans lexemple suivant :
package app.construction; import java.awt.Point; import java.lang.reflect.Constructor;

Chapitre 14

Introduction la construction

155

public class ShowReflection { public static void main(String args[]) { Constructor[] cc = Point.class.getConstructors(); Constructor cons = null; for (int i = 0; i < cc.length; i++) if (cc[i].getParameterTypes().length == 2) cons = cc[i];

Exercice 14.3 Quest-ce que le programme ShowReflection produit en sortie ?

La rexion vous permet datteindre des rsultats qui sont autrement difciles ou impossibles atteindre.

Rsum
Dordinaire, vous fournissez des classes avec des constructeurs pour en permettre linstanciation. Ceux-ci peuvent former une suite collaborative et chaque constructeur doit au nal invoquer le constructeur de la super-classe. La mthode classique dappel dun constructeur est lemploi de loprateur new mais vous pouvez aussi recourir la rexion pour instancier et utiliser des objets.

Au-del de la construction ordinaire


Le mcanisme de constructeur dans Java offre de nombreuses options de conception de classe. Toutefois, un constructeur dune classe nest efcace que si lutilisateur sait quelle classe instancier et connat les champs requis pour linstanciation. Par exemple, le choix des composants dune GUI crer peut dpendre du matriel sur lequel le programme doit sexcuter. Un quipement portable naura pas la mme surface dafchage quun ordinateur. Il peut aussi arriver quun dveloppeur sache quelle classe instancier mais ne possde pas toutes les valeurs initiales, ou quil les ait dans le mauvais format. Par exemple, le dveloppeur peut avoir besoin de crer un objet partir dune version dormante ou textuelle dun objet. Dans une telle situation, lemploi ordinaire de constructeurs Java ne suft pas et vous devez recourir un pattern de conception.

156

Partie III

Patterns de construction

Le tableau suivant dcrit lobjectif de patterns qui facilitent la construction.


Si vous envisagez de Collecter progressivement des informations sur un objet avant de demander sa construction Diffrer la dcision du choix de la classe instancier Construire une famille dobjets qui partagent certains aspects Spcier un objet crer en donnant un exemple Reconstruire un objet partir dune version dormante ne contenant que ltat interne de lobjet Appliquez le pattern

BUILDER FACTORY METHOD ABSTRACT FACTORY PROTOTYPE MEMENTO

Lobjectif de chaque pattern de conception est de permettre la rsolution dun problme dans un certain contexte. Les patterns de construction permettent un client de construire un nouvel objet par lintermdiaire de moyens autres que lappel dun constructeur de classe. Par exemple, lorsque vous obtenez progressivement les valeurs initiales dun objet, vous pouvez envisager dappliquer le pattern BUILDER.

15
BUILDER
Vous ne disposez pas toujours de toutes les informations ncessaires pour crer un objet lorsque vient le moment de le construire. Il est particulirement pratique de permettre la construction progressive dun objet, au rythme de lobtention des paramtres pour le constructeur, comme cela se produit avec lemploi dun analyseur syntaxique ou avec une interface utilisateur. Cela peut aussi tre utile lorsque vous souhaitez simplement rduire la taille dune classe dont la construction est relativement complique sans que cette complexit ait rellement de rapport avec le but principal de la classe. Lobjectif du pattern BUILDER est de dplacer la logique de construction dun objet en dehors de la classe instancier.

Un objet constructeur ordinaire


Une situation banale dans laquelle vous pouvez tirer parti du pattern BUILDER est celle o les donnes qui dnissent lobjet voulu sont incorpores dans une chane de texte. A mesure que votre code examine, ou analyse, les donnes, vous devez les stocker telles que vous les trouvez. Que votre analyseur sappuie sur XML ou soit une cration personnelle, il est possible que vous ne disposiez initialement pas de sufsamment de donnes pour construire lobjet voulu. La solution fournie par BUILDER est denregistrer les donnes extraites du texte dans un objet intermdiaire jusqu ce que le programme soit prt lui demander de construire lobjet partir de ces donnes.

158

Partie III

Patterns de construction

Supposez quen plus de fabriquer des fuses, Oozinoz organise parfois des feux dartice. Les agences de voyages envoient des requtes de rservation dans le format suivant :
Date, November 5, Headcount, 250, City, Springfield, DollarsPerHead, 9,95, HasSite, False

Comme vous lavez sans doute remarqu, ce protocole remonte une poque antrieure XML (Extensible Markup Language), mais il sest montr sufsant jusqu prsent. La requte signale quand un client potentiel souhaite organiser un feu dartice et dans quelle ville cela doit se passer. Elle spcie aussi le nombre de personnes (Headcount) minimal garanti par le client et le prix par tte (DollarsPerHead) que le client accepte de payer. Le client, dans cet exemple, souhaite organiser un show pour 250 invits et est prt payer $9,95 par personne, soit un total de $2 487,50. Lagence de voyages indique aussi que le client na pas de site lesprit (False) pour le droulement du show. La tche raliser consiste analyser le texte de la requte et crer un objet Reservation reprsentant celle-ci. Nous pourrions accomplir cela en crant un objet Reservation vide et en dnissant ses paramtres mesure que notre analyseur (parser) les rencontre. Le problme est quun objet Reservation pourrait ne pas reprsenter une requte valide. Par exemple, nous pourrions terminer lanalyse du texte et raliser quil manque une date. Pour nous assurer quun objet Reservation reprsente toujours une requte valide, nous pouvons utiliser une classe ReservationBuilder. Lobjet ReservationBuilder peut stocker les attributs dune requte de rservation mesure que lanalyseur les trouve, puis crer un objet Reservation en vriant sa validit. La Figure 15.1 illustre les classes dont nous avons besoin pour cette conception. La classe ReservationBuilder est abstraite ainsi que sa mthode build(). Nous crerons des sous-classes ReservationBuilder concrtes qui varieront au niveau de linsistance avec laquelle elles tentent de crer un objet Reservation lorsque les donnes sont incompltes. Le constructeur de la classe ReservationParser attend un builder NDT : nous utiliserons ce terme pour diffrencier lobjet de stockage du constructeur traditionnel auquel passer des informations.

Chapitre 15

BUILDER

159

Reservation

ReservationBuilder

Reservation( date:Date, headcount:int, city:String, dollarsPerHead:double, hasSite:bool)

futurize(:Date):Date getCity():String setCity(:String) getDate():date setDate(:date) getDollarsPerHead():Dollars

ReservationParser -builder:ReservationBuilder ReservationParser( :ReservationBuilder) parse(s:String)

setDollarsPerHead(:Dollars) hasSite():bool setHasSite(:bool) getHeadcount():int setHeadcount(:int) build():Reservation

Figure 15.1
Une classe builder libre une classe spcique de la logique de construction et peut accepter progressivement des paramtres dinitialisation mesure quun analyseur syntaxique les dcouvre.

La mthode parse() extrait des informations dune chane de rservation et les transmet au builder, comme dans lextrait suivant :
public void parse(String s) throws ParseException { String[] tokens = s.split(","); for (int i = 0; i < tokens.length; i += 2) { String type = tokens[i]; String val = tokens[i + 1]; if ("date".compareToIgnoreCase(type) == 0) { Calendar now = Calendar.getInstance(); DateFormat formatter = DateFormat.getDateInstance(); Date d = formatter.parse( val + ", " + now.get(Calendar.YEAR)); builder.setDate(ReservationBuilder.futurize(d)); } else if ("headcount".compareToIgnoreCase(type) == 0) builder.setHeadcount(Integer.parseInt(val));

160

Partie III

Patterns de construction

else if ("City".compareToIgnoreCase(type) == 0) builder.setCity(val.trim()); else if ("DollarsPerHead".compareToIgnoreCase(type)==0) builder.setDollarsPerHead( new Dollars(Double.parseDouble(val))); else if ("HasSite".compareToIgnoreCase(type) == 0) builder.setHasSite(val.equalsIgnoreCase("true")); } }

Le code de parse() utilise une mthode String.split() pour diviser, ou dcouper, la chane fournie en entre. Le code attend une rservation sous forme dune liste de types dinformation et de valeurs spars par une virgule. La mthode String.compareToIgnoreCase() permet la comparaison de ne pas tenir compte de la casse. Lorsque lanalyseur rencontre le mot "date", il examine la valeur qui suit et la place dans le futur. La mthode futurize() avance lanne de la date jusqu ce que cette dernire soit situe dans le futur. A mesure que vous progresserez dans lexamen du code, vous remarquerez plusieurs endroits o lanalyseur pourrait sgarer, commencer par le dcoupage initial de la chane de rservation. Exercice 15.1 Lobjet dexpression rgulire utilis par les appels de split() divise une liste de valeurs spares par des virgules en chanes individuelles. Suggrez une amlioration de cette expression rgulire, ou de lensemble de lapproche, qui permettra lanalyseur de mieux reconnatre les informations de rservation.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B. Construction avec des contraintes
Vous devez vous assurer que les objets Reservation invalides ne soient jamais instancis. Plus spciquement, supposez que toute rservation doit avoir une valeur non nulle pour la date et la ville. Supposez aussi quune rgle mtier stipule quOozinoz ne ralisera pas le show pour moins de 25 personnes ou moins de $495,95. Ces limites pourraient tre enregistres dans une base de donnes, mais pour linstant nous les reprsenterons sous forme de constantes dans le code Java, comme dans lexemple suivant :
public abstract class ReservationBuilder { public static final int MINHEAD = 25;

Chapitre 15

BUILDER

161

public static final Dollars MINTOTAL = new Dollars(495.95); // ... }

Pour viter la cration dune instance de Reservation lorsquune requte est invalide, vous pourriez placer les contrles de logique mtier et les gnrations dexceptions dans le constructeur pour Reservation. Cette logique est toutefois relativement indpendante de la fonction normale dun objet Reservation une fois celui-ci cr. Lintroduction dun builder permettra de simplier la classe Reservation en ne laissant que des mthodes ddies dautres fonctions que la construction. Lemploi dun builder donne aussi la possibilit de valider les paramtres dun objet Reservation en proposant des ractions diffrentes en cas de paramtres invalides. Finalement, dplac au niveau dune sous-classe ReservationBuilder, le travail de construction peut se drouler progressivement mesure que lanalyseur dcouvre les valeurs des attributs de rservation. La Figure 15.2 illustre des sousclasses ReservationBuilder concrtes qui diffrent dans leur faon de tolrer des paramtres invalides.

ReservationBuilder

build():Reservation

UnforgivingBuilder

Exception

build():Reservation

throws

BuilderException

ForgivingBuilder

build():Reservation

throws

Figure 15.2
Les objets builders peuvent diffrer dans leur niveau de sensibilit et de gnration dexceptions en cas de chane de rservation incomplte.

162

Partie III

Patterns de construction

Le diagramme de la Figure 15.2 met en valeur un avantage dappliquer le pattern BUILDER. En sparant la logique de construction de la classe Reservation, nous pouvons traiter la construction comme une tche distincte et mme crer une hirarchie dapproches distincte. Les diffrences de comportement lors de la construction ont peu de rapport avec la logique de rservation. Par exemple, les builders dans la Figure 15.2 diffrent dans leur niveau de sensibilit pour ce qui est de la gnration dune exception BuilderException. Un code utilisant un builder ressemblera lextrait suivant :
package app.builder; import com.oozinoz.reservation.*; public class ShowUnforgiving { public static void main(String[] args) { String sample = "Date, November 5, Headcount, 250, " + "City, Springfield, DollarsPerHead, 9.95, " + "HasSite, False"; ReservationBuilder builder = new UnforgivingBuilder(); try { new ReservationParser(builder).parse(sample); Reservation res = builder.build(); System.out.println("Builder non tolrant : " + res); } catch (Exception e) { System.out.println(e.getMessage()); } } }

Lexcution de ce programme afche un objet Reservation:


Date: Nov 5, 2001, Headcount: 250, City: Springfield, Dollars/Head: 9.95, Has Site: false

A partir dune chane de requte de rservation, le code instancie un builder et un analyseur, et demande celui-ci danalyser la chane. A mesure quil lit la chane, lanalyseur transmet les attributs de rservation au builder en utilisant ses mthodes set. Aprs lanalyse, le code demande au builder de construire une rservation valide. Cet exemple afche simplement le texte dun message dexception au lieu dentreprendre une action plus consquente comme ce serait le cas pour une relle application.

Chapitre 15

BUILDER

163

Exercice 15.2 La mthode build() de la classe UnforgivingBuilder gnre une exception BuilderException si la valeur de la date ou de la ville est null, si le nombre de personnes est trop bas, ou si le cot total de la rservation propose est trop faible. Ecrivez le code de la mthode build() en fonction de ces spcications.

Un builder tolrant
La classe UnforgivingBuilder rejette toute requte comportant la moindre erreur. Une meilleure rgle de gestion serait dapporter des changements raisonnables aux requtes auxquelles il manque certains dtails concernant la rservation. Supposez quun analyste dOozinoz vous demande de dnir le nombre de personnes un minimum si cette valeur dattribut est omise. De mme, si le prix accept par tte est manquant, le builder pourrait dnir cet attribut pour que le cot total soit suprieur au minimum requis. Ces exigences sont simples, mais la conception ncessite quelque rexion. Par exemple, que devra faire le builder si une chane de rservation fournit un cot par tte sans indiquer le nombre de personnes ? Exercice 15.3 Rdigez une spcication pour ForgivingBuilder.build() en prvoyant ce que le builder devrait faire en cas domission du nombre de personnes ou du prix par tte.

Exercice 15.4 Aprs avoir revu votre approche, crivez le code de la mthode build() pour la classe ForgivingBuilder.

Les classes ForgivingBuilder et UnforgivingBuilder garantissent que les objets Reservation seront toujours valides. Votre conception apporte aussi de la souplesse quant laction entreprendre en cas de problme dans la construction dune rservation.

164

Partie III

Patterns de construction

Rsum
Le pattern BUILDER spare la construction dun objet complexe de sa reprsentation. Il sensuit une simplication du processus de construction. Il permet une classe de se concentrer sur la construction correcte dun objet en permettant la classe principale de se concentrer sur le fonctionnement dune instance valide. Cela est particulirement utile lorsque vous voulez garantir la validit dun objet avant de linstancier et ne souhaitez pas que la logique associe apparaisse dans le constructeur de la classe. Un objet builder rend aussi possible une construction progressive, ce qui se produit souvent lorsque vous crez un objet partir de lanalyse dun texte.

16
FACTORY METHOD
Lorsque vous dveloppez une classe, vous fournissez gnralement des constructeurs pour permettre aux clients de linstancier. Cependant, un client qui a besoin dun objet ne sait pas, ou ne devrait pas savoir, quelle classe instancier parmi plusieurs choix possibles. Lobjectif du pattern FACTORY METHOD est de laisser un autre dveloppeur dnir linterface permettant de crer un objet, tout en gardant un contrle sur le choix de la classe instancier.

Un exemple classique : des itrateurs


Le pattern ITERATOR (itrateur) offre un moyen daccder de manire squentielle aux lments dune collection (voir le Chapitre 28), mais FACTORY METHOD sous-tend souvent la cration des itrateurs. La version 1.2 du JDK a introduit une interface Collection qui inclut une mthode iterator(); toutes les collections limplmentent. Cette opration vite que lappelant ait savoir quelle classe instancier. Une mthode iterator() cre un objet qui retourne une squence forme des lments dune collection. Par exemple, le code suivant cre et afche le contenu dune liste :
package app.factoryMethod; import java.util.*; public class ShowIterator { public static void main(String[] args) { List list = Arrays.asList( new String[] { "fountain", "rocket", "sparkler"});

166

Partie III

Patterns de construction

Iterator iter = list.iterator(); while (iter.hasNext()) System.out.println(iter.next()); } }

Exercice 16.1 Quelle est la classe relle de lobjet Iterator dans ce code ?

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.


Le pattern FACTORY METHOD dcharge le client du souci de savoir quelle classe instancier.

Identication de FACTORY METHOD


Vous pourriez penser que nimporte quelle mthode crant et retournant un nouvel objet est forcment une mthode factory. Cependant, dans la programmation oriente objet, les mthodes qui retournent de nouveaux objets sont chose courante, et elles ne sont pas toutes des instances de FACTORY METHOD. Exercice 16.2 Nommez deux mthodes frquemment utilises des bibliothques de classes Java qui retournent un nouvel objet. Le fait quune mthode cre un nouvel objet ne signie pas ncessairement quil sagit dun exemple de FACTORY METHOD. Une mthode factory est une opration qui non seulement produit un nouvel objet mais vite au client de savoir quelle classe instancier. Dans une conception FACTORY METHOD, vous trouvez plusieurs classes qui implmentent la mme opration retournant le mme type abstrait, mais, lors de la demande de cration dun nouvel objet, la classe qui est effectivement instancie dpend du comportement de lobjet factory recevant la requte. Exercice 16.3 Le nom de la classe javax.swing.BorderFactory semble indiquer un exemple du pattern FACTORY METHOD. Expliquez en quoi lobjectif du pattern diffre de celui de cette classe.

Chapitre 16

FACTORY METHOD

167

Garder le contrle sur le choix de la classe instancier


En gnral, un client qui requiert un objet instancie la classe voulue en utilisant un de ses constructeurs. Il se peut aussi parfois que le client ne sache pas exactement quelle classe instancier. Cela peut se produire, par exemple, dans le cas ditrateurs, la classe requise dpendant du type de collection que le client souhaite parcourir, mais aussi frquemment dans du code dapplication. Supposez quOozinoz soit prt laisser les clients acheter des feux dartice crdit. Ds le dbut du dveloppement du systme dautorisation de crdit, vous acceptez de prendre en charge la conception dune classe CreditCheckOnline dont lobjectif sera de vrier si un client peut disposer dun certain montant de crdit chez Oozinoz. En entamant le dveloppement, vous ralisez que lorganisme de crdit sera parfois hors ligne. Lanalyste du projet dtermine que, dans ce cas, il faut que le reprsentant du centre de rception des appels puisse disposer dune bote de dialogue pour prendre une dcision sur la base de quelques questions. Vous crez donc une classe CreditCheckOffline et implmentez le processus en respectant les spcications. Initialement, vous concevez les classes comme illustr Figure 16.1. La mthode creditLimit() accepte un numro didentication de client et retourne sa limite de crdit. Avec les classes de la Figure 16.1, vous pouvez fournir des informations de limite de crdit, que lorganisme de crdit soit ou non en ligne. Le problme qui se prsente maintenant est que lutilisateur de vos classes doit connatre la classe instancier, mais vous tes celui qui sait si lorganisme est ou non disponible. Dans ce scnario, vous devez vous appuyer sur linterface pour crer un objet, mais garder le contrle sur le choix de la classe instancier. Une solution possible est de changer les deux classes pour implmenter une interface standard et crer une mthode factory qui retourne un objet de ce type. Spciquement, vous pourriez :
m m

faire une interface Java CreditCheck qui inclut la mthode creditLimit(); changer les deux classes de contrle de crdit an quelles implmentent linterface CreditCheck; crer une classe CreditCheckFactory avec une mthode createCreditCheck() qui retournera un objet de type CreditCheck.

168

Partie III

Patterns de construction

Figure 16.1
Une de ces classes sera instancie pour vrier la limite de crdit dun client.

com.oozinoz.credit

CreditCheckOnline

creditLimit(id:int):Dollars

CreditCheckOffline

creditLimit(id:int):Dollars

En implmentant createCreditCheck(), vous utiliserez vos informations de disponibilit de lorganisme de crdit pour dcider de la classe instancier. Exercice 16.4 Dessinez un diagramme de classes pour cette nouvelle stratgie, qui permet de crer un objet de vrication de crdit tout en conservant la matrise sur le choix de la classe instancier.

Grce limplmentation de FACTORY METHOD, lutilisateur de vos services pourra appeler la mthode createCreditCheck() et obtenir un objet de contrle de crdit qui fonctionnera indpendamment de la disponibilit de lagence. Exercice 16.5 Supposez que la classe CreditCheckFactory comprenne une mthode isAgencyUp() indiquant si lagence est disponible et crivez le code pour createCreditCheck().

Chapitre 16

FACTORY METHOD

169

Application de FACTORY METHOD dans une hirarchie parallle


Le pattern FACTORY METHOD apparat souvent lorsque vous utilisez une hirarchie parallle pour modliser un domaine de problmes. Une hirarchie parallle est une paire de hirarchies de classes dans laquelle chaque classe dune hirarchie possde une classe correspondante dans lautre hirarchie. Une telle conception intervient gnralement lorsque vous dcidez de dplacer un sous-ensemble doprations hors dune hirarchie dj existante. Considrez la construction de bombes ariennes comme illustr au Chapitre 5. Pour les fabriquer, Oozinoz utilise des machines organises selon le modle du diagramme prsent la Figure 16.2.
Figure 16.2
La hirarchie Machine intgre une logique de contrle des machines physiques et de planication.
Machine

getAvailable():Date start() stop() ...

Mixer

StarPress

ShellAssembler

Fuser

Pour concevoir une bombe, des substances sont mlanges dans un mixeur (Mixer) puis passes une presse extrudeuse (StarPress) qui produit des granules, ou toiles. Celles-ci sont tasses dans une coque sphrique contenant en son centre de la poudre noire et le tout est plac au-dessus dune chasse, ou charge de propulsion, au moyen dune assembleuse (ShellAssembler). Un dispositif dallumage est ensuite insr (Fuser), lequel servira la mise feu de la charge de propulsion et celle de la coque centrale. Imaginez que vous vouliez que la mthode getAvailable() prvoie le moment o une machine termine le traitement en cours et est disponible pour un autre travail.

170

Partie III

Patterns de construction

Cela peut ncessiter lemploi de diverses mthodes prives qui ajouteront un certain volume de code chacune de nos classes de machines. Plutt que dajouter la logique de planication la hirarchie Machine, vous pourriez prfrer utiliser une hirarchie MachinePlanner distincte. Vous avez besoin dune classe de planication distincte pour la plupart des types de machines, sauf pour les mixeurs et les sertisseuses de dispositifs dallumage, qui sont toujours disponibles pour du travail supplmentaire et peuvent se sufre dune classe BasicPlanner. Exercice 16.6 Compltez le diagramme de la hirarchie parallle Machine/MachinePlanner de la Figure 16.3.
Figure 16.3
Epurez la hirarchie Machine en dplaant la logique de planication vers une hirarchie parallle.
Machine MachinePlanner #machine:Machine createPlanner():?? MachinePlanner( m:Machine) getAvailable():Date ??

??

??

??

??

??

??

Exercice 16.7 Ecrivez une mthode createPlanner() pour que la classe Machine retourne un objet BasicPlanner, et crivez une mthode createPlanner() pour la classe StarPress.

Chapitre 16

FACTORY METHOD

171

Rsum
Lobjectif du pattern FACTORY METHOD est de permettre un fournisseur de services dexonrer le client du besoin de savoir quelle classe instancier. Ce pattern intervient dans la bibliothque de classes Java, notamment dans la mthode iterator() de linterface Collection. FACTORY METHOD se prsente souvent au niveau du code du client, lorsquil est ncessaire de dcharger les clients de la ncessit de connatre la classe partir de laquelle crer un objet. Ce besoin disoler le client peut apparatre lorsque le choix de la classe instancier dpend dun facteur dont le client na pas connaissance, comme la disponibilit dun service externe. Vous pouvez galement rencontrer FACTORY METHOD lorsque vous introduisez une hirarchie parallle pour viter quun ensemble de classes soit encombr par de nombreux aspects comportementaux. Vous pouvez ainsi relier des hirarchies en permettant aux sous-classes dune hirarchie de dterminer quelle classe instancier dans la hirarchie correspondante.

17
ABSTRACT FACTORY
Comme nous lavons vu dans le chapitre prcdent, il est parfois utile, lors de la cration dobjets, de garder un contrle sur le choix de la classe instancier. Dans ce cas, vous pouvez appliquer le pattern FACTORY METHOD avec une mthode qui utilise un facteur externe pour dterminer la classe instancier. Dans certaines circonstances, ce facteur peut tre thmatique, couvrant plusieurs classes. Lobjectif du pattern ABSTRACT FACTORY, ou KIT, est de permettre la cration de familles dobjets ayant un lien ou interdpendants.

Un exemple classique : le kit de GUI


Un kit de GUI est un exemple classique dapplication du pattern ABSTRACT FACTORY. Cest un objet factory abstrait qui fournit les composants graphiques un client laborant une interface utilisateur. Il dtermine lapparence que revtent les boutons, les champs de texte ou tout autre lment. Un kit tablit un style spcique, en dnissant les couleurs darrire-plan, les formes, ou autres aspects dune GUI. Vous pourriez ainsi tablir un certain style pour la totalit dun systme ou, au l du temps, introduire des changements dans une application existante, par exemple pour reter un changement de version ou une modication des graphiques standards de la socit. Ce pattern permet ainsi dapporter de la convivialit, de contribuer un apprentissage et une utilisation plus aiss dune application en jouant sur son apparence. La Figure 17.1 illustre un exemple avec la classe UI dOozinoz.

174

Partie III

Patterns de construction

Figure 17.1
Les instances de la classe UI sont des objets factory qui crent des familles de composants de GUI.
UI NORMAL:UI createButton():JButton getFont():Font createPaddedPanel(c:Component):JPanel getIcon(imageName:String):Icon

BetaUI

Les sous-classes de la classe UI peuvent rednir nimporte quel lment de lobjet factory. Une application qui construit une GUI partir dune instance de la classe UI peut par la suite produire un style diffrent en se fondant sur une instance dune sous-classe de UI. Par exemple, Oozinoz utilise une classe Visualization pour aider les ingnieurs mettre en place de nouvelles lignes de fabrication. Lcran de visualisation est illustr Figure 17.2.
Figure 17.2
Cette application ajoute des machines dans la partie suprieure gauche de la fentre et laisse lutilisateur les positionner par un glisser-dposer. Il peut annuler un ajout ou un positionnement.

Chapitre 17

ABSTRACT FACTORY

175

Lapplication de visualisation de la Figure 17.2 permet un utilisateur dajouter des machines et de les dplacer au moyen de la souris le programme qui afche cette visualisation est ShowVisualization dans le rpertoire app.abstractFactory. Lapplication obtient ses boutons partir dun objet UI que la classe Visualization accepte dans son constructeur. La Figure 17.3 illustre la classe Visualization.
Figure 17.3
La classe Visualization construit une GUI au moyen dun objet factory UI.
Visualization UI

Visualization(ui:UI) #undoButton():JButton ...

La classe Visualization construit sa GUI laide dun objet UI. Par exemple, le code de la mthode undoButton() se prsente comme suit :
protected JButton undoButton() { if (undoButton == null) { undoButton = ui.createButtonCancel(); undoButton.setText("Undo"); undoButton.setEnabled(false); undoButton.addActionListener(mediator.undoAction()); } return undoButton; }

Ce code cre un bouton dannulation et modie son texte (pour quil indique "Undo"). La classe UI dtermine la taille et la position de limage et du texte sur le bouton. Le code gnrateur de bouton de la classe UI se prsente comme suit :
public JButton createButton() { JButton button = new JButton(); button.setSize(128, 128); button.setFont(getFont()); button.setVerticalTextPosition(AbstractButton.BOTTOM); button.setHorizontalTextPosition(AbstractButton.CENTER); return button; } public JButton createButtonOk() { JButton button = createButton(); button.setIcon(getIcon("images/rocket-large.gif")); button.setText("Ok!"); return button; }

176

Partie III

Patterns de construction

public JButton createButtonCancel() { JButton button = createButton(); button.setIcon(getIcon("images/rocket-large-down.gif")); button.setText("Cancel!"); return button; }

An de gnrer un autre style pour lapplication de visualisation des machines, nous pouvons crer une sous-classe qui rednit certains des lments de la classe factory UI. Nous pourrons ensuite passer une instance de cette nouvelle classe factory au constructeur de la classe Visualization. Supposez que nous ayons introduit une nouvelle version de la classe Visualization avec des fonctionnalits supplmentaires. Pendant sa phase de bta-test, nous dcidons de changer linterface utilisateur. Nous aimerions en fait avoir des polices en italiques et substituer aux images de fuses des images provenant des chiers cherry-large.gif et cherry-largedown.gif. Voici un exemple de code dune classe BetaUI drive de UI:
public class BetaUI extends UI { public BetaUI () { Font oldFont = getFont(); font = new Font( oldFont.getName(), oldFont.getStyle() | Font.ITALIC, oldFont.getSize()); } public JButton createButtonOk() { // Exercice ! } public JButton createButtonCancel() { // Exercice ! } }

Exercice 17.1 Compltez le code pour la classe BetaUI.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

Chapitre 17

ABSTRACT FACTORY

177

Le code suivant excute la visualisation avec le nouveau style :


package app.abstractFactory; // ... public class ShowBetaVisualization { public static void main(String[] args) { JPanel panel = new Visualization(new BetaUI()); SwingFacade.launch(panel, "Operational Model"); } }

Ce programme excute la visualisation avec lapparence illustre Figure 17.4. Les instances de UI et de BetaUI fournissent diffrentes familles de composants graphiques an de proposer diffrents styles. Bien que ce soit une application utile du pattern ABSTRACT FACTORY, la conception est quelque peu fragile. En particulier, la classe BetaUI dpend de la possibilit de rednir les mthodes charges de la cration et daccder certaines variables dinstance dclares protected, notamment font, de la classe UI.
Figure 17.4
Sans changement dans le code de la classe Visualization, lapplication afche la nouvelle interface produite par la classe BetaUI.

Exercice 17.2 Suggrez un changement de conception qui permettrait toujours de dvelopper une varit dobjets factory, mais en rduisant la dpendance des sous-classes lgard des modicateurs de mthodes de la classe UI.

178

Partie III

Patterns de construction

Le pattern ABSTRACT FACTORY affranchit les clients du besoin de savoir quelles classes instancier lorsquils ncessitent de nouveaux objets. A cet gard, il sapparente un ensemble de mthodes FACTORY METHOD. Dans certains cas, une conception FACTORY METHOD peut voluer en une conception ABSTRACT FACTORY.

Classe FACTORY abstraite et pattern FACTORY METHOD


Le Chapitre 16 a introduit une paire de classes implmentant linterface CreditCheck. Dans la conception prsente, la classe CreditCheckFactory instancie une de ces classes lorsquun client appelle sa mthode createCreditCheck(), et la classe qui est instancie dpend de la disponibilit de lorganisme de crdit. Cette conception vite aux autres dveloppeurs dtre dpendants de cette information. La Figure 17.5 illustre la classe CreditCheckFactory et les implmentations de linterface CreditCheck.
Figure 17.5
Une conception
CreditCheckFactory

FACTORY METHOD
qui exonre le code client de lobligation de connatre la classe instancier pour vrier des informations de crdit.
createCreditCheck():CreditCheck

interface CreditCheck CreditCheckOnline

creditLimit(id:int):Dollars

CreditCheckOffline

La classe CreditCheckFactory fournit dhabitude des informations provenant de lorganisme de crdit sur la limite autorise pour un client donn. En outre, le package credit possde des classes qui peuvent rechercher des informations dexpdition et de facturation pour un client. La Figure 17.6 illustre le package com.oozinoz.credit original. Supposez maintenant quun analyste des besoins dOozinoz vous signale que la socit est prte prendre en charge les clients vivant au Canada. Pour travailler avec le Canada, vous utiliserez un autre organisme de crdit ainsi que dautres sources de donnes pour dterminer les informations dexpdition et de facturation.

Chapitre 17

ABSTRACT FACTORY

179

com.oozinoz.credit

CreditCheckFactory

isAgencyUp():boolean createCreditCheck():CreditCheck

interface CreditCheck

CreditCheckOnline

CreditCheckOffline

ShippingCheck

BillingCheck

hasTariff()

isResidential()

Figure 17.6
Les classes dans ce package vrient le crdit dun client, ladresse dexpdition et ladresse de facturation.

Lorsquun client tlphone, lapplication utilise par le centre de rception des appels doit recourir une famille dobjets pour effectuer une varit de contrles. Cette famille devra tre diffrente selon que lappel proviendra du Canada ou des Etats-Unis. Vous pouvez appliquer le pattern ABSTRACT FACTORY pour permettre la cration de ces familles dobjets. Lexpansion de lactivit au Canada doublera pratiquement le nombre de classes sous-tendant les vrications de crdit. Supposez que vous dcidiez de coder ces classes dans trois packages. Le package credit contiendra maintenant trois interfaces "Check" et une classe factory abstraite. Cette classe aura trois mthodes de cration pour gnrer les objets appropris chargs de vrier les informations de crdit, de facturation et denvoi. Vous inclurez aussi la classe CreditCheckOffline dans ce package, partant du principe que vous pourrez lutiliser pour effectuer

180

Partie III

Patterns de construction

les contrles en cas dindisponibilit de lorganisme de crdit indpendamment de lorigine dun appel. La Figure 17.7 montre la nouvelle composition du package com.oozinoz.credit.
Figure 17.7
Le package revu contient principalement des interfaces et une classe factory abstraite.
com.oozinoz.credit

interface CreditCheck

CreditCheckOffline

creditLimit(id:int)

interface BillingCheck

CreditCheckFactory

isResidential()

interface ShippingCheck

isAgencyUp():bool createCreditCheck() createBillingCheck() createShippingCheck()

hasTariff()

Pour implmenter les interfaces de credit avec des classes concrtes, vous pouvez introduire deux nouveaux packages : com.oozinoz.credit.ca et com.oozinoz.credit.us. Chacun de ces packages peut contenir une version concrte de la classe factory et des classes pour implmenter chacune des interfaces de credit. Les classes factory concrtes pour les appels provenant du Canada et des Etats-Unis sont relativement simples. Elles retournent les versions canadiennes ou tats-uniennes des interfaces "Check", sauf si lorganisme de crdit local est hors ligne, auquel cas elles retournent toutes deux un objet CreditCheckOffline. Comme dans le chapitre prcdent, la classe CreditCheckFactory possde une mthode isAgencyUp() qui indique si lorganisme de crdit est disponible.

Chapitre 17

ABSTRACT FACTORY

181

com.oozinoz.credit.ca

com.oozinoz.credit

CheckFactoryCanada

CreditCheckFactory

interface BillingCheck

interface ShippingCheck

interface CreditCheck

Figure 17.8
Les classes du package com.oozinoz.credit.ca et leurs relations avec les classes de com.oozinoz.credit.

Exercice 17.4 Compltez le code pour CheckFactoryCanada.java:


package com.oozinoz.credit.ca; import com.oozinoz.credit.*; public class CheckFactoryCanada extends CreditCheckFactory { // Exercice ! }

A ce stade, vous disposez dune conception qui applique le pattern ABSTRACT FACTORY pour permettre la cration de familles dobjets chargs de vrier diffrentes informations concernant un client. Une instance de la classe CreditCheckFactory abstraite sera soit de la classe CheckFactoryCanada, soit de la classe CheckFactoryUS, et les objets de contrle gnrs seront appropris pour le pays reprsent par lobjet factory.

182

Partie III

Patterns de construction

Packages et classes factory abstraites


On peut quasiment dire quun package contient habituellement une famille de classes, et quune classe factory abstraite produit une famille dobjets. Dans lexemple prcdent, vous avez utilis des packages distincts pour supporter des classes factory abstraites pour le Canada et les Etats-Unis, avec un troisime package fournissant des interfaces communes pour les objets produits par les classes factory. Exercice 17.5 Justiez la dcision de placer chaque classe factory et ses classes associes dans un package distinct, ou argumentez en faveur dune autre approche juge suprieure.

Rsum
Le pattern ABSTRACT FACTORY vous permet de prvoir la possibilit pour un client de crer des objets faisant partie dune famille dobjets entretenant une relation. Une application classique de ce pattern concerne la cration de familles de composants de GUI, ou kits. Dautres aspects peuvent aussi tre traits sous forme de familles dobjets, tels que le pays de rsidence dun client. Comme pour FACTORY METHOD, ABSTRACT FACTORY vous permet dexonrer le client de la ncessit de savoir quelle classe instancier pour crer un objet, en vous permettant de lui fournir une classe factory produisant des objets lis par un aspect commun.

18
PROTOTYPE
Lorsque vous dveloppez une classe, vous prvoyez habituellement des constructeurs pour permettre aux applications clientes de linstancier. Il y a toutefois des situations o vous souhaitez empcher le code utilisateur de vos classes dappeler directement un constructeur. Les patterns orients construction dcrits jusqu prsent dans cette partie, BUILDER, FACTORY METHOD et ABSTRACT FACTORY, offrent tous la possibilit de mettre en place ce type de prvention en tablissant des mthodes qui instancient une classe pour le compte du client. Le pattern PROTOTYPE dissimule galement la cration dun objet mais emploie une approche diffrente. Lobjectif du pattern PROTOTYPE est de fournir de nouveaux objets par la copie dun exemple plutt que de produire de nouvelles instances non initialises dune classe.

Des prototypes en tant quobjets factory


Supposez que vous utilisiez le pattern ABSTRACT FACTORY chez Oozinoz pour fournir des interfaces utilisateurs pour diffrents contextes. La Figure 18.1 illustre les classes factory de GUI pouvant voluer.
Figure 18.1
Trois classes factory abstraites, ou kits, pour produire diffrents styles de GUI.
UIKit HandheldUI

createButton() createGrid() createGroupBox() createPaddedPanel() font():Font ...

WideScreenUI

BetaUI

184

Partie III

Patterns de construction

Les utilisateurs dOozinoz apprcient la productivit rsultant du fait de pouvoir appliquer une GUI approprie pour un contexte donn. Le problme que vous rencontrez est que vos utilisateurs souhaitent plusieurs kits de GUI de plus, alors quil devient encombrant de crer une nouvelle classe pour chaque contexte envisag par eux. Pour stopper la prolifration des classes factory de GUI, un dveloppeur dOozinoz suggre lapplication du pattern PROTOTYPE de la manire suivante :
m m

supprimer les sous-classes de UIKit; faire en sorte que chaque instance de UIKit devienne une factory de GUI qui fonctionne en gnrant des copies de composants prototypes ; placer le code qui cre de nouveaux objets UIKit dans des mthodes statiques de la classe UIKit.

Avec cette conception, un objet UIKit aura un jeu complet de variables prototypes dinstance : un objet bouton, un objet grille, un objet panneau avec relief de remplissage, etc. Le code qui crera un nouvel objet UIKit dnira les valeurs des composants prototypes an de produire lapparence dsire. Les mthodes de cration, create-(), retourneront des copies de ces composants. Par exemple, nous pouvons crer une mthode statique handheldUI() de la classe UI. Cette mthode instanciera UIKit, dnira les variables dinstance avec des valeurs appropries pour un cran dquipement portable, et retournera lobjet utiliser en tant que kit de GUI. Exercice 18.1 Une conception selon PROTOTYPE diminuera le nombre de classes quOozinoz utilise pour grer plusieurs kits de GUI. Citez deux avantages ou inconvnients supplmentaires lis cette approche.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.


La faon normale de crer un objet est dinvoquer un constructeur dune classe. Le pattern PROTOTYPE offre une solution souple, permettant de dterminer au moment de lexcution lobjet utiliser en tant que modle pour le nouvel objet. Cette approche dans Java ne permet cependant pas de nouveaux objets davoir des mthodes diffrentes de celles de leur parent. Il vous faudra donc valuer les avantages et les inconvnients de cette technique et procder son exprimentation pour dterminer si elle rpond vos besoins. Pour pouvoir lappliquer, vous devrez matriser les mcanismes de la copie dobjets dans Java.

Chapitre 18

PROTOTYPE

185

Prototypage avec des clones


Lobjectif du pattern PROTOTYPE est de fournir de nouveaux objets en copiant un exemple. Lorsque vous copiez un objet, le nouvel objet aura les mmes attributs et le mme comportement que ses parents. Le nouvel objet peut galement hriter de certaines ou de toutes les valeurs de donnes de lobjet parent. Par exemple, une copie dun panneau avec relief de remplissage devrait avoir la mme valeur de remplissage que loriginal. Il est important de se demander ceci : lorsque vous copiez un objet, lopration fournit-elle des copies des valeurs dattributs de lobjet original ou la copie partaget-elle ces valeurs avec loriginal ? Il est facile doublier de se poser cette question ou dy rpondre de faon incorrecte. Les dfauts apparaissent souvent lorsque les dveloppeurs font des suppositions errones sur les mcanismes de la copie. De nombreuses classes dans les bibliothques de classes Java offrent un support pour la copie, mais en tant que dveloppeur, vous devez comprendre comment la copie fonctionne, surtout si vous voulez utiliser PROTOTYPE. Exercice 18.2 La classe Object inclut une mthode clone() dont tous les objets hritent. Si cette mthode ne vous est pas familire, reportez-vous laide en ligne ou la documentation. Ecrivez ensuite dans vos propres termes ce que cette mthode effectue.

Exercice 18.3 Supposez que la classe Machine possdait deux attributs : un entier ID et un emplacement, Location, sous forme dune classe distincte. Dessinez un diagramme objet montrant un objet Machine, son objet Location, et tout autre objet rsultant de lappel de clone() sur lobjet Machine.

La mthode clone() facilite lajout dune mthode copy() une classe. Par exemple, vous pourriez crer une classe de panneaux pouvant tre clons au moyen du code suivant :
package com.oozinoz.ui; import javax.swing.JPanel;

186

Partie III

Patterns de construction

public class OzPanel extends JPanel implements Cloneable { // Dangereux ! public OzPanel copy() { return (OzPanel) this.clone(); } // ... }

Figure 18.2
La classe OzPanel hrite dun grand nombre de champs et de variables de ses super-classes.
java.lang.Object

javax.swing.Component // Plus de 40 champs getBackground() getFont() getForeground() // Davantage de mthodes

java.awt.Container // Plus de 10 champs // Toujours plus de mthodes

java.awt.JComponent // Plus de 20 champs // Plus de mthodes

javax.swing.JPanel

com.oozinoz.ui.OzPanel

Chapitre 18

PROTOTYPE

187

La mthode copy() dans ce code rend le clonage public et convertit la copie dans le type adquat. Le problme de ce code est que la mthode clone() crera des copies de tous les attributs dun objet JPanel, indpendamment du fait que vous compreniez ou non la fonction de ces attributs. Notez que les attributs de la classe JPanel incluent les attributs des classes anctres, comme le montre la Figure 18.2. Comme le suggre la Figure 18.2, la classe OzPanel hrite dun nombre important de proprits de la classe Component, et ce sont souvent les seuls attributs quil vous faut copier lorsque vous travaillez avec des objets de GUI. Exercice 18.4 Ecrivez une mthode OzPanel.copy2() qui copie un panneau sans sappuyer sur clone(). Supposez que les seuls attributs importants pour une copie sont background, font et foreground.

Rsum
Le pattern PROTOTYPE permet un client de crer de nouveaux objets en copiant un exemple. Une grande diffrence entre appeler un constructeur et copier un objet est quune copie inclut gnralement un certain tat de lobjet original. Vous pouvez utiliser cela votre avantage, surtout lorsque diffrentes catgories dobjets ne diffrent que par leurs attributs et non dans leurs comportements. Dans ce cas, vous pouvez crer de nouvelles classes au moment de lexcution en gnrant des objets prototypes que le client peut copier. Lorsque vous devez crer une copie, la mthode Object.clone() peut tre utile, mais vous devez vous rappeler quelle cre un nouvel objet avec les mmes champs. Cet objet peut ne pas tre une copie convenable, et toute difcult lie une opration de copie plus importante relve de votre responsabilit. Si un objet prototype possde trop de champs, vous pouvez crer un nouvel objet par instanciation et en dnissant ses champs de manire ne reprsenter que les aspects de lobjet original que vous voulez copier.

19
MEMENTO
Il y a des situations o lobjet que vous voulez crer existe dj. Cela se produit lorsque vous voulez laisser un utilisateur annuler des oprations, revenir une version prcdente dun travail, ou reprendre un travail suspendu. Lobjectif du pattern MEMENTO est de permettre le stockage et la restauration de ltat dun objet.

Un exemple classique : dfaire une opration


Le Chapitre 17 a introduit une application de visualisation permettant ses utilisateurs dexprimenter la modlisation des ux matriels dans une usine. Supposez que la fonctionnalit du bouton Undo nait pas encore t implmente. Nous pouvons appliquer le pattern MEMENTO pour faire fonctionner ce bouton. Un objet mmento contient des informations dtat. Dans lapplication de visualisation, ltat que nous devons prserver est celui de lapplication. Lors de lajout ou du dplacement dune machine, un utilisateur devrait tre en mesure dannuler lopration en cliquant sur le bouton Undo. Pour ajouter cette fonctionnalit, nous devons dcider de la faon de capturer ltat de lapplication dans un objet mmento. Nous devrons aussi dcider du moment auquel le faire, et comment le restaurer au besoin. Lorsque lapplication dmarre, elle apparat comme illustr Figure 19.1. Lapplication dmarre vierge, ce qui est malgr tout un tat. Dans ce cas, le bouton Undo devrait tre dsactiv. Aprs quelques ajouts et dplacements, la fentre pourrait ressembler lexemple de la Figure 19.2.

190

Partie III

Patterns de construction

Figure 19.1
Lorsque lapplication dmarre, le panneau est vierge et le bouton Undo est dsactiv.

Figure 19.2
Lapplication aprs quelques ajouts et positionnements de machines.

Ltat quil nous faut enregistrer dans un mmento est une liste des emplacements des machines qui ont t places par lutilisateur. Nous pouvons empiler ces mmentos, en en dpilant un chaque fois que lutilisateur clique sur le bouton Undo :
m

Chaque fois que lutilisateur ajoute ou dplace une machine, le code doit crer un mmento du factory simul et lajouter une pile.

Chapitre 19

MEMENTO

191

Chaque fois quil clique sur le bouton Undo, le code doit retirer le mmento le plus rcent, le plus haut dans la pile, et restaurer la simulation dans ltat qui y aura t enregistr.

Lorsque lapplication dmarre, vous empilez un mmento vide qui nest jamais prlev pour garantir que le sommet de la pile sera toujours un mmento valide. Chaque fois que la pile ne contient quun mmento, vous dsactivez le bouton Undo. Nous pourrions crire le code de ce programme dans une seule classe, mais nous envisageons lajout de fonctionnalits pour grer la modlisation oprationnelle et dautres fonctions que les utilisateurs pourront ventuellement demander. Finalement, lapplication pouvant devenir plus grande, il est sage de sappuyer sur une conception MVC (Modle-Vue-Contrleur) . La Figure 19.3 illustre une conception qui place le travail de modlisation de lobjet factory en classes distinctes.
Figure 19.3
Cette conception divise le travail de lapplication en classes distinctes, pour modliser lobjet factory, fournir les lments de GUI et grer les clics de lutilisateur.
Visualization VisMediator

FactoryModel

Cette conception vous permet de vous concentrer dabord sur le dveloppement dune classe FactoryModel qui ne possde pas de composants de GUI et aucune dpendance lgard de la GUI. La classe FactoryModel est au cur de la conception. Elle est responsable de la gestion de la conguration actuelle des machines et des mmentos des congurations antrieures. Chaque fois quun client demande lobjet factory dajouter ou de dplacer une machine, celui-ci cre une copie, un objet mmento, de lemplacement actuel des machines, et place lobjet sur la pile de mmentos. Dans cet exemple, nous navons pas besoin dune classe Memento spciale. Chaque mmento est simplement une liste de points : la liste des emplacements de lquipement un moment donn.

192

Partie III

Patterns de construction

Le modle de conception de lusine doit prvoir des vnements pour permettre aux clients de senregistrer pour signaler leur intrt connatre les changements dtat de lusine. Cela permet la GUI dinformer le modle de changements que lutilisateur effectue. Supposez que vous vouliez que le factory laisse les clients senregistrer pour connatre les vnements dajout et de dplacement de machine. La Figure 19.4 illustre une conception pour une classe FactoryModel.

FactoryModel -mementos:Stack -listeners:ArrayList add(loc:Point) drag(oldLoc:Point,newLoc:Point) getLocations:List canUndo:boolean undo() notifyListeners() addChangeListener(:ChangeListener)

Stack

ArrayList

Figure 19.4
La classe FactoryModel conserve une pile de congurations matrielles et permet aux clients de senregistrer pour tre notis des changements intervenant dans lusine.

La conception de la Figure 19.4 prvoit que la classe FactoryModel donne aux clients la possibilit de senregistrer pour tre notis de plusieurs vnements. Par exemple, considrez lvnement dajout dune machine. Tout objet ChangeListener enregistr sera noti de ce changement :
package com.oozinoz.visualization; // ... public class FactoryModel { private Stack mementos; private ArrayList listeners = new ArrayList(); public FactoryModel() {

Chapitre 19

MEMENTO

193

mementos = new Stack(); mementos.push(new ArrayList()); } //... }

Le constructeur dbute la conguration initiale de lusine sous forme dune liste vierge. Les autres mthodes de la classe grent la pile des mmentos et dclenchent les vnements qui correspondent tout changement. Par exemple, pour ajouter une machine la conguration actuelle, un client peut appeler la mthode suivante :
public void add(Point location) { List oldLocs = (List) mementos.peek(); List newLocs = new ArrayList(oldLocs); newLocs.add(0, location); mementos.push(newLocs); notifyListeners(); }

Ce code cre une nouvelle liste demplacements des machines et la place sur la pile gre par le modle. Une subtilit du code est de sassurer que la nouvelle machine soit dabord dans cette liste. Cest un signe pour la visualisation quelle doit alors apparatre devant les autres machines que lafchage pourrait faire se chevaucher. Un client qui senregistre pour recevoir les notications de changements pourrait actualiser la vue de son modle en se reconstruisant lui-mme entirement la rception dun vnement de la part du modle. La conguration la plus rcente du modle est toujours disponible dans getLocations(), dont le code se prsente ainsi :
public List getLocations() { return (List) mementos.peek(); }

La mthode undo() de la classe FactoryModel permet un client de changer le modle de positionnement de machines pour restituer une version prcdente. Lorsque ce code sexcute, il invoque aussi notifyListeners(). Exercice 19.1 Ecrivez le code de la mthode undo() de la classe FactoryModel.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

194

Partie III

Patterns de construction

Un client intress peut fournir une fonctionnalit dannulation doprations en enregistrant un listener et en fournissant une mthode qui reconstruit la vue du modle. La classe Visualization est un client de ce type. La conception MVC illustre la Figure 19.3 spare les tches dinterprtation des actions de lutilisateur de celles de gestion de la GUI. La classe Visualization cre ses composants de GUI mais fait passer la gestion des vnements de GUI un mdiateur. La classe VisMediator traduit les vnements de GUI en changements appropris dans le modle. Lorsque celui-ci change, la GUI peut ncessiter une actualisation. La classe Visualization senregistre pour recevoir les notications fournies par la classe FactoryModel. Notez la sparation des responsabilits.
m m

La visualisation change les vnements du modle en changements de la GUI. Le mdiateur traduit les vnements de GUI en changements du modle.

La Figure 19.5 illustre en dtail les trois classes qui collaborent.


Figure 19.5
Le mdiateur traduit les vnements de GUI en changements du modle, et la visualisation ragit aux vnements de changements du modle pour actualiser la GUI.
Visualization

addButton() buttonPanel() createPictureBox() machinePanel() undoButton() main() stateChanged()

VisMediator

addAction() mouseDownAction() mouseMotionAction() undoAction()

FactoryModel

Supposez que, pendant le dplacement dune image de machine, un utilisateur la dpose accidentellement au mauvais endroit et clique sur le bouton Undo. Pour pouvoir grer ce clic, la visualisation enregistre le mdiateur pour quil reoive les

Chapitre 19

MEMENTO

195

notications dvnements de bouton. Le code du bouton Undo dans la classe Visualization se prsente comme suit :
protected JButton undoButton() { if (undoButton == null) { undoButton = ui.createButtonCancel(); undoButton.setText("Undo"); undoButton.setEnabled(false); undoButton.addActionListener(mediator.undoAction()); } return undoButton; }

Ce code dlgue au mdiateur la responsabilit de la gestion dun clic. Le mdiateur informe le modle de tout changement demand et traduit une requte dannulation dopration en un changement du modle au moyen du code suivant :
private void undo(ActionEvent e) { factoryModel.undo(); }

La variable factoryModel dans cette mthode est une instance de FactoryModel, que la classe Visualization cre et passe au mdiateur via le constructeur de la classe VisMediator. Nous avons dj examin la commande pop() de la classe FactoryModel. Le ux de messages qui est gnr lorsque lutilisateur clique sur Undo est prsent Figure 19.6.

:JButton

:FactoryModel

:VisMediator

:Visualization

undo() undo() stateChanged()

Figure 19.6
Le diagramme illustre les messages gnrs suite lactivation du bouton Undo.

196

Partie III

Patterns de construction

Lorsque la classe FactoryModel prlve de la pile la conguration prcdente quelle a stocke en tant que mmento, la mthode undo() notie les ChangeListeners. La classe Visualization a prvu son enregistrement cet effet dans son constructeur :
public Visualization(UI ui) { super(new BorderLayout()); this.ui = ui; mediator = new VisMediator(factoryModel); factoryModel.addChangeListener(this); add(machinePanel(), BorderLayout.CENTER); add(buttonPanel(), BorderLayout.SOUTH); }

Pour chaque position de machine dans le modle, la visualisation conserve un objet Component quil cre avec la mthode createPictureBox(). La mthode stateChanged() doit nettoyer tous les composants en place dans le panneau et rtablir les encadrs des positions restaures. La mthode stateChanged() doit aussi dsactiver le bouton Undo sil ne reste quun mmento sur la pile. Exercice 19.2 Ecrivez la mthode stateChanged() pour la classe Visualization.

Le pattern MEMENTO permet de sauvegarder et de restaurer ltat dun objet. Une application courante de ce pattern est la gestion de la fonctionnalit dannulation doprations dans les applications. Dans certaines applications, comme dans lexemple de visualisation des machines de lusine, lentrept o stocker les informations sauvegardes peut tre un autre objet. Dans dautres cas, les mmentos peuvent tre stocks sous une forme plus durable.

Dure de vie des mmentos


Un mmento est un petit entrept qui conserve ltat dun objet. Vous pouvez crer un mmento en utilisant un autre objet, une chane ou un chier. La dure anticipe entre le stockage et la reconstruction dun objet a un impact sur la stratgie que vous utilisez dans la conception dun mmento. Il peut sagir dun court instant, mais aussi dheures, de jours ou dannes.

Chapitre 19

MEMENTO

197

Exercice 19.3 Indiquez deux raisons qui peuvent motiver lenregistrement dun mmento dans un chier plutt que sous forme dobjet.

Persistance des mmentos entre les sessions


Une session se produit lorsquun utilisateur excute un programme, ralise des transactions par son intermdiaire, puis le quitte. Supposez que vos utilisateurs souhaitent pouvoir sauvegarder une simulation dune session et la restaurer dans une autre session. Cette fonctionnalit est un concept normalement appel stockage persistant. Le stockage persistant satisfait lobjectif du pattern MEMENTO et constitue une extension naturelle de la fonctionnalit dannulation que nous avons dj implmente. Supposez que vous driviez une sous-classe Visualization2 de la classe Visualization, qui possde une barre de menus avec un menu File comportant les options Save As et Restore From :
package com.oozinoz.visualization; import javax.swing.*; import com.oozinoz.ui.SwingFacade; import com.oozinoz.ui.UI; public class Visualization2 extends Visualization { public Visualization2(UI ui) { super(ui); } public JMenuBar menus() { JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("File"); menuBar.add(menu); JMenuItem menuItem = new JMenuItem("Save As..."); menuItem.addActionListener(mediator.saveAction()); menu.add(menuItem); menuItem = new JMenuItem("Restore From..."); menuItem.addActionListener( mediator.restoreAction()); menu.add(menuItem); return menuBar; }

198

Partie III

Patterns de construction

public static void main(String[] args) { Visualization2 panel = new Visualization2(UI.NORMAL); JFrame frame = SwingFacade.launch( panel, "Operational Model"); frame.setJMenuBar(panel.menus()); frame.setVisible(true); } }

Ce code requiert lajout des mthodes saveAction() et restoreAction() la classe VisMediator. Les objets MenuItem provoquent lappel de ces actions lorsque le menu est slectionn. Lorsque la classe Visualization2 sexcute, la GUI se prsente comme illustr Figure 19.7.
Figure 19.7
Lajout dun menu permet lutilisateur denregistrer un mmento que lapplication pourra restaurer lors dune prochaine session.

Un moyen facile de stocker un objet, telle la conguration du modle de visualisation, est de le srialiser. Le code de la mthode saveAction() dans la classe VisMediator pourrait tre comme suit :
public ActionListener saveAction() { return new ActionListener() { public void actionPerformed(ActionEvent e) { try { VisMediator.this.save((Component)e.getSource());

Chapitre 19

MEMENTO

199

} catch (Exception ex) { System.out.println( "Echec de sauvegarde : " + ex.getMessage()); } }}; } public void save(Component source) throws Exception { JFileChooser dialog = new JFileChooser(); dialog.showSaveDialog(source); if (dialog.getSelectedFile() == null) return; FileOutputStream out = null; ObjectOutputStream s = null; try { out = new FileOutputStream(dialog.getSelectedFile()); s = new ObjectOutputStream(out); s.writeObject(factoryModel.getLocations()); } finally { if (s != null) s.close(); } }

Exercice 19.4 Ecrivez le code de la mthode restoreAction() de la classe VisMediator.

Louvrage Design Patterns dcrit ainsi lobjectif du pattern MEMENTO: "Sans enfreindre les rgles dencapsulation, il capture et externalise ltat interne dun objet an de pouvoir le restaurer ultrieurement." Exercice 19.5 Dans ce cas, nous avons utilis la srialisation Java pour enregistrer la conguration dans un chier au format binaire. Supposez que nous layons enregistr dans le format XML (texte). Expliquez brivement pourquoi, votre avis, lenregistrement dun mmento au format texte serait une atteinte la rgle dencapsulation.

200

Partie III

Patterns de construction

Vous devriez comprendre ce quun dveloppeur signie lorsquil indique quil cre des mmentos en stockant les donnes dobjets au moyen de la srialisation ou de lenregistrement dans un chier XML. Cest lide des patterns de conception : en utilisant un vocabulaire commun, nous pouvons discuter de concepts de conception et de leurs applications.

Rsum
Le pattern MEMENTO permet de capturer ltat dun objet de manire pouvoir le restaurer ultrieurement. La mthode de stockage utilise cet effet dpend du type de restauration faire, aprs un clic ou une frappe au clavier ou lors dune prochaine session aprs un certain temps. La raison la plus courante de raliser cela est toutefois de supporter la fonction dannulation dactions prcdentes dans une application. Dans ce cas, vous pouvez stocker ltat dun objet dans un autre objet. Pour que le stockage de ltat soit persistant, vous pouvez utiliser, entre autres moyens, la srialisation dobjet.

IV
Patterns dopration

20
Introduction aux oprations
Lorsque vous crivez une mthode Java, vous produisez une unit fondamentale de traitement qui intervient un niveau au-dessus de celui de lcriture dune instruction. Vos mthodes doivent participer une conception, une architecture et un plan de test densemble, et en mme temps lcriture de mthodes est au cur de la POO. En dpit de ce rle central, une certaine confusion rgne quant ce que les mthodes sont vraiment et comment elles fonctionnent, qui vient probablement du fait que les dveloppeurs et les auteurs ont tendance utiliser de faon interchangeable les termes mthode et opration. De plus, les concepts dalgorithme et de polymorphisme, bien que plus abstraits que les mthodes, sont au nal mis en uvre par elles. Une dnition claire des termes algorithme, polymorphisme, mthode et opration vous aidera comprendre plusieurs patterns de conception. En particulier, STATE, STRATEGY et INTERPRETER fonctionnent tous trois en implmentant une opration dans des mthodes travers plusieurs classes, mais une telle observation na dutilit que si nous nous entendons sur le sens de mthode et dopration.

Oprations et mthodes
Parmi les nombreux termes relatifs au traitement quune classe peut tre amene effectuer, il est particulirement utile de distinguer une opration dune mthode. Le langage UML dnit cette diffrence comme suit :
m

Une opration est la spcication dun service qui peut tre demand par une instance dune classe. Une mthode est limplmentation dune opration.

204

Partie IV

Patterns dopration

Notez que la signication dopration se situe un niveau dabstraction au-dessus de la notion de mthode. Une opration spcie quelque chose quune classe accomplit et spcie linterface pour appeler ce service. Plusieurs classes peuvent implmenter la mme opration de diffrentes manires. Par exemple, nombre de classes implmentent lopration toString() chacune sa faon. Chaque classe qui implmente une opration utilise pour cela une mthode. Cette mthode contient ou est le code qui permet lopration de fonctionner pour cette classe. Les dnitions de mthode et opration permettent de clarier la structure de nombreux patterns de conception. Etant donn que ces patterns se situent un niveau au-dessus des classes et des mthodes, il nest pas surprenant que les oprations soient prdominantes dans de nombreux patterns. Par exemple, COMPOSITE permet dappliquer des oprations la fois des lments et des groupes, et PROXY permet un intermdiaire implmentant les mmes oprations quun objet cible de sinterposer pour grer laccs cet objet. Exercice 20.1 Utilisez les termes opration et mthode pour expliquer comment le pattern CHAIN OF RESPONSIBILITY implmente une opration.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

Dans Java, la dclaration dune mthode inclut un en-tte (header) et un corps (body). Le corps est la srie dinstructions qui peuvent tre excutes en invoquant la signature de la mthode. Len-tte inclut le type de retour et la signature de la mthode et peut aussi inclure des modicateurs et une clause throws. Voici le format de len-tte dune mthode : modicateurs type-retour signature clause-throws Exercice 20.2 Parmi les neuf modicateurs de mthodes Java, numrez tous ceux que vous pouvez.

Chapitre 20

Introduction aux oprations

205

Signatures
En surface, la signication dopration est semblable celle de signature, ces deux termes dsignant linterface dune mthode. Lorsque vous crivez une mthode, elle devient invocable conformment sa signature. La Section 8.4.2 de louvrage JavaTM Language Specication [Arnold et Gosling 1998] donne la dnition suivante dune signature : La signature dune mthode est constitue du nom de la mthode ainsi que du nombre et des types de paramtres formels quelle reoit. Notez que la signature dune mthode ninclut pas son type de retour. Toutefois, si la dclaration dune mthode remplace la dclaration dune autre mthode, une erreur de compilation surviendra si elles possdent des types de retour diffrents. Exercice 20.3 La mthode Bitmap.clone() retourne toujours une instance de la classe Bitmap bien que son type de retour soit Object. Serait-elle compile sans erreur si son type de retour tait Bitmap?

Une signature spcie quelle mthode est invoque lorsquun client effectue un appel. Une opration est la spcication dun service qui peut tre demand. Les termes signature et opration ont une signication analogue, bien quils ne soient pas synonymes. La diffrence a trait principalement au contexte dans lequel ils sappliquent. Le terme opration est employ en rapport avec lide que des mthodes de diffrentes classes peuvent avoir la mme interface. Le terme signature est employ en rapport avec les rgles qui dterminent comment Java fait correspondre lappel dune mthode une mthode de lobjet rcepteur. Une signature dpend du nom et des paramtres dune mthode mais pas du type de retour de celle-ci.

Exceptions
Dans son livre La maladie comme mtaphore, Susan Sontag observe ceci : "En naissant, nous acqurons une double nationalit qui relve du royaume des bienportants comme de celui des malades." Cette mtaphore peut aussi sappliquer aux mthodes : au lieu de se terminer normalement, une mthode peut gnrer une

206

Partie IV

Patterns dopration

exception ou invoquer une autre mthode pour cela. Lorsquune mthode se termine normalement, le contrle du programme revient au point situ juste aprs lappel. Un autre ensemble de rgles sappliquent dans le royaume des exceptions. Lorsquune exception est gnre, lenvironnement dexcution Java doit trouver une instruction try/catch correspondante. Cette instruction peut exister dans la mthode qui a gnr lexception, dans la mthode qui a appel la mthode courante, ou dans la mthode qui a appel la mthode prcdente, et ainsi de suite en remontant la pile dappels. En labsence dinstruction try/catch correspondante, le programme plante. Nimporte quelle mthode peut gnrer une exception en utilisant une instruction throw. Par exemple :
throw new Exception("Bonne chance !");

Si votre application utilise une mthode qui gnre une exception que vous navez pas prvue, cela peut la faire planter. Pour viter ce genre de comportement, vous devez disposer dun plan architectural qui spcie les points dans votre application o les exceptions sont interceptes et gres de faon approprie. Vous pensez probablement quil nest pas trs commode davoir dclarer lventualit dune exception. Dans C#, par exemple, les mthodes nont pas besoin de dclarer des exceptions. Dans C++, une exception peut apparatre sans que le compilateur doive vrier quelle a t prvue par les appelants. Exercice 20.4 Contrairement Java, C# nimpose pas aux mthodes de dclarer les exceptions quelles peuvent tre amenes gnrer. Pensez-vous que Java constitue une amlioration cet gard ? Expliquez votre rponse.

Algorithmes et polymorphisme
Les algorithmes et le polymorphisme sont deux concepts importants en programmation, mais il peut tre difcile de donner une explication de ces termes. Si vous voulez montrer quelquun une mthode, vous pouvez modier le code source dune classe en mettant en vidence les lignes de code appropries. Parfois, un algorithme peut exister entirement dans une mthode, mais il sappuie le plus souvent sur linteraction de plusieurs mthodes. Louvrage Introduction to

Chapitre 20

Introduction aux oprations

207

Algorithms (Introduction lalgorithmique) [Cormen, Leiserson, et Rivest 1990, p. 1] afrme ceci : Un algorithme est une procdure de calcul bien dnie qui reoit une ou plusieurs valeurs en entre et produit une ou plusieurs valeurs en sortie. Un algorithme est une procdure, cest--dire une squence dinstructions, qui accepte une entre et produit un rsultat. Comme il a t dit, une seule mthode peut constituer un algorithme : elle accepte une entre sa liste de paramtres et produit sa valeur de retour en sortie. Toutefois, nombre dalgorithmes requirent plusieurs mthodes pour sexcuter dans un programme orient objet. Par exemple, lalgorithme isTree() du Chapitre 5, consacr au pattern COMPOSITE, ncessite quatre mthodes, comme le montre la Figure 20.1.
Figure 20.1
Quatre mthodes isTree() forment lalgorithme et collaborent pour dterminer si une instance de MachineComponent est un arbre.
MachineComponent

isTree():bool isTree(:Set):bool

Machine

MachineComposite

isTree(:Set):bool

isTree(:Set):bool

Exercice 20.5 Combien dalgorithmes, doprations et de mthodes la Figure 20.1 comprendelle ?

Un algorithme ralise un traitement. Il peut tre contenu dans une seule mthode ou bien ncessiter de nombreuses mthodes. En POO, les algorithmes qui requirent plusieurs mthodes sappuient souvent sur le polymorphisme pour autoriser

208

Partie IV

Patterns dopration

plusieurs implmentations dune mme opration. Le polymorphisme est le principe selon lequel la mthode appele dpend la fois de lopration invoque et de la classe du rcepteur de lappel. Par exemple, vous pourriez vous demander quelle mthode est excute lorsque Java rencontre lexpression m.isTree(). Cela dpend. Si m est une instance de Machine, Java invoquera Machine.isTree(). Sil sagit dune instance de MachineComposite, il invoquera MachineComposite.isTree(). De manire informelle, le polymorphisme signie que la mthode approprie sera invoque pour le type dobjet appropri. Nombre de patterns emploient le principe de polymorphisme, lequel est parfois directement li lobjectif du pattern.

Rsum
Mme si les termes opration, mthode, signature et algorithme semblent souvent avoir une signication proche, prserver leur distinction facilite la description de concepts importants. A linstar dune signature, une opration est la spcication dun service. Le terme opration est employ en rapport avec lide que de nombreuses mthodes peuvent avoir la mme interface. Le terme signature est employ en rapport avec les rgles de recherche de la mthode approprie. La dnition dune mthode inclut sa signature, cest--dire son nom et sa liste de paramtres, ainsi que des modicateurs, un type de retour et le corps de la mthode. Une mthode possde une signature et implmente une opration. La voie normale dinvocation dune mthode consiste lappeler. Une mthode doit normalement se terminer en retournant une valeur, mais interrompra son excution si elle rencontre une exception non gre. Un algorithme est une procdure qui accepte une entre et produit un rsultat. Une mthode accepte elle aussi une entre et produit un rsultat, et comme elle contient un corps procdural, certains auteurs qualient ce corps dalgorithme. Mais tant donn que la procdure algorithmique peut faire intervenir de nombreuses oprations et mthodes, ou peut exister au sein dune mme mthode, il est plus correct de rserver le terme algorithme pour dsigner une procdure produisant un rsultat. Beaucoup de patterns de conception impliquent la distribution dune opration travers plusieurs classes. On peut galement dire que ces patterns sappuient sur le polymorphisme, principe selon lequel la slection dune mthode dpend de la classe de lobjet qui reoit lappel.

Chapitre 20

Introduction aux oprations

209

Au-del des oprations ordinaires


Diffrentes classes peuvent implmenter une opration de diffrentes manires. Autrement dit, Java supporte le polymorphisme. La puissance de ce concept pourtant simple apparat dans plusieurs patterns de conception.
Si vous envisagez de Implmenter un algorithme dans une mthode, remettant plus tard la dnition de certaines tapes de lalgorithme pour permettre des sous-classes de les rednir Distribuer une opration an que chaque classe reprsente un tat diffrent Encapsuler une opration, rendant les implmentations interchangeables Encapsuler un appel de mthode dans un objet Distribuer une opration de faon que chaque implmentation sapplique un type diffrent de composition Appliquez le pattern

TEMPLATE METHOD

STATE STRATEGY COMMAND INTERPRETER

Les patterns dopration conviennent dans des contextes o vous avez besoin de plusieurs mthodes, gnralement avec la mme signature, pour participer une conception. Par exemple, le pattern TEMPLATE METHOD permet des sous-classes dimplmenter des mthodes qui ajustent leffet dune procdure dnie dans une super-classe.

21
TEMPLATE METHOD
Les mthodes ordinaires ont un corps qui dnit une squence dinstructions. Il est galement assez commun pour une mthode dinvoquer des mthodes de lobjet courant et dautres objets. Dans ce sens, les mthodes ordinaires sont des "modles" (template) qui exposent une srie dinstructions que lordinateur doit suivre. Le pattern TEMPLATE METHOD implique un type plus spcique de modle. Lorsque vous crivez une mthode, vous pouvez vouloir dnir la structure gnrale dun algorithme tout en laissant la possibilit dimplmenter diffremment certaines tapes. Dans ce cas, vous pouvez dnir la mthode mais faire de ces tapes des mthodes abstraites, des mthodes stub, ou des mthodes dnies dans une interface spare. Cela produit un modle plus rigide qui dnit spciquement quelles tapes dun algorithme peuvent ou doivent tre fournies par dautres classes. Lobjectif du pattern TEMPLATE METHOD est dimplmenter un algorithme dans une mthode, laissant dautres classes le soin de dnir certaines tapes de lalgorithme.

Un exemple classique : algorithme de tri


Les algorithmes de tri ne datent pas dhier et sont hautement rutilisables. Imaginez quun homme prhistorique ait labor une mthode pour trier des ches en fonction du degr dafftage de leur tte. La mthode consiste aligner les ches puis effectuer une srie de permutations gauche-droite, remplaant chaque che par

212

Partie IV

Patterns dopration

une che plus affte situe sur sa gauche. Cet homme pourrait ensuite appliquer la mme mthode pour trier les ches selon leur porte ou tout autre critre. Les algorithmes de tri varient en termes dapproche et de rapidit, mais tous se fondent sur le principe primitif de comparaison de deux lments ou attributs. Si vous disposez dun algorithme de tri et pouvez comparer un certain attribut de deux lments, lalgorithme vous permettra dobtenir une collection dlments tris daprs cet attribut. Le tri est un exemple de TEMPLATE METHOD. Il sagit dune procdure qui nous permet de modier une tape critique, savoir la comparaison de deux objets, an de pouvoir rutiliser lalgorithme pour divers attributs de diffrentes collections dobjets. A notre poque, le tri est probablement lalgorithme le plus frquemment rimplment, le nombre dimplmentations dpassant vraisemblablement le nombre de programmeurs. Mais moins davoir trier une norme collection, vous navez gnralement pas besoin dcrire votre propre algorithme. Les classes Arrays et Collections fournissent une mthode sort() statique qui reoit comme argument un tableau trier ainsi quun Comparator optionnel. La mthode sort() de la classe ArrayList est une mthode dinstance qui dtermine le rcepteur du message sort(). A un autre gard, ces mthodes partagent une stratgie commune qui dpend des interfaces Comparable et Comparator, comme illustr Figure 21.1. Les mthodes sort() des classes Arrays et Collections vous permettent de fournir une instance de linterface Comparator si vous le souhaitez. Si vous employez une mthode sort() sans fournir une telle instance, elle sappuiera sur la mthode compareTo() de linterface Comparable. Une exception surviendra si vous tentez de trier des lments sans fournir une instance de Comparator et que ces lments nimplmentent pas linterface Comparable. Mais notez que les types les plus rudimentaires, tels que String, implmentent Comparable. Les mthodes sort() reprsentent un exemple de TEMPLATE METHOD. Les bibliothques de classes incluent un algorithme qui vous permet de fournir une tape critique : la comparaison de deux lments. La mthode compare() retourne un nombre infrieur, gal, ou suprieur 0. Ces valeurs correspondent lide que, dans le sens que vous dnissez, lobjet o1 est infrieur, gal, ou suprieur lobjet o2. Par exemple, le code suivant trie une collection de fuses en fonction de leur apoge

Chapitre 21

TEMPLATE METHOD

213

Figure 21.1
La mthode sort() de la classe Collections utilise les interfaces prsentes ici.
java.util interface Comparator

compare(o1:Object,o2:Object):int

Collections

sort(l: List, c:Comparator)

java.lang interface Comparable

compareTo(obj:Object):int

puis de leur nom (le constructeur de Rocket reoit le nom, la masse, le prix, lapoge et la pousse de la fuse) :
package app.templateMethod; import java.util.Arrays; import com.oozinoz.firework.Rocket; import com.oozinoz.utility.Dollars; public class ShowComparator { public static void main(String args[]) { Rocket r1 = new Rocket( "Sock-it", 0.8, new Dollars(11.95), 320, 25); Rocket r2 = new Rocket( "Sprocket", 1.5, new Dollars(22.95), 270, 40); Rocket r3 = new Rocket( "Mach-it", 1.1, new Dollars(22.95), 1000, 70);

214

Partie IV

Patterns dopration

Rocket r4 = new Rocket( "Pocket", 0.3, new Dollars(4.95), 150, 20); Rocket[] rockets = new Rocket[] { r1, r2, r3, r4 }; System.out.println("Tries par apoge : "); Arrays.sort(rockets, new ApogeeComparator()); for (int i = 0; i < rockets.length; i++) System.out.println(rockets[i]); System.out.println(); System.out.println("Tries par nom : "); Arrays.sort(rockets, new NameComparator()); for (int i = 0; i < rockets.length; i++) System.out.println(rockets[i]); } }

Voici le comparateur ApogeeComparator:


package app.templateMethod; import java.util.Comparator; import com.oozinoz.firework.Rocket; public class ApogeeComparator implements Comparator { // Exercice ! }

Voici le comparateur NameComparator:


package app.templateMethod; import java.util.Comparator; import com.oozinoz.firework.Rocket; public class NameComparator implements Comparator { // Exercice ! }

Lafchage du programme dpend de la faon dont Rocket implmente toString() mais montre les fuses tries des deux manires :
Tries par apoge : Pocket Sprocket Sock-it Mach-it

Chapitre 21

TEMPLATE METHOD

215

Tries par nom : Mach-it Pocket Sock-it Sprocket

Exercice 21.1 Ecrivez le code manquant dans les classes ApogeeComparator et NameComparator pour que le programme puisse trier correctement une collection de fuses.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

Le tri est un algorithme gnral qui, lexception dune tape, na rien voir avec les spcicits de votre domaine ou application. Cette tape critique est la comparaison dlments. Aucun algorithme ninclut, par exemple, dtape pour comparer les apoges de deux fuses. Votre application doit donc fournir cette tape. Les mthodes sort() et linterface Comparator vous permettent dinsrer une tape spcique dans un algorithme de tri gnral. TEMPLATE METHOD ne se limite pas aux cas o seule ltape manquante est propre un domaine. Parfois, lalgorithme entier sapplique un domaine particulier.

Compltion dun algorithme


Les patterns TEMPLATE METHOD et ADAPTER sont semblables en ce quils permettent tous deux un dveloppeur de simplier et de spcier la faon dont le code dun autre dveloppeur complte une conception. Dans ADAPTER, un dveloppeur peut spcier linterface dun objet requis par la conception, et un autre peut crer un objet qui fournit linterface attendue mais en utilisant les services dune classe existante possdant une interface diffrente. Dans TEMPLATE METHOD, un dveloppeur peut fournir un algorithme gnral, et un autre fournir une tape essentielle de lalgorithme. Considrez la presse toiles de la Figure 21.2. La presse toiles fabrique par la socit Aster Corporation accepte des moules en mtal vides et presse dedans des toiles de feu dartice. La machine possde des trmies qui dispensent les produits chimiques quelle mlange en une pte et presse dans les moules. Lorsquelle sarrte, elle interrompt son traitement du moule qui

216

Partie IV

Patterns dopration

Figure 21.2
Une presse toiles Aster possde des tapis dentre et de sortie qui transportent les moules. Oozinoz ajoute un tapis de rcupration qui collecte la pte dcharge.
Dchargement de la pte

Tapis de rcupration

Zone de travail Tapis d'entre Tapis de sortie

se trouve dans la zone de travail, et transfre tous les moules de son tapis dentre vers son tapis de sortie sans les traiter. Elle dcharge ensuite son stock de pte et rince grande eau sa zone de travail. Elle orchestre toute cette activit en utilisant un ordinateur de bord et la classe AsterStarPress illustre Figure 21.3.
Figure 21.3
Les presses toiles Aster utilisent une classe abstraite dont vous devez driver une sous-classe pour pouvoir vous en servir chez Oozinoz.
Code Aster Code Oozinoz

AsterStarPress

OzAsterStarPress

dischargePaste() flush() inProcess():bool markMoldIncomplete( moldID:int) shutDown() stopProcessing() usherInputMolds()

markMoldIncomplete( moldID:int)

MaterialManager

getManager(): MaterialManager setMoldIncomplete( moldID:int)

Chapitre 21

TEMPLATE METHOD

217

La presse toiles Aster est intelligente et indpendante, et a t conue pour pouvoir oprer dans une unit de production intelligente avec laquelle elle doit communiquer. Par exemple, la mthode shutDown() notie lunit de production lorsque le moule en cours de traitement est incomplet :
public void shutdown() { if (inProcess()) { stopProcessing(); markMoldIncomplete(currentMoldID); } usherInputMolds(); dischargePaste(); flush(); }

La mthode markMoldIncomplete() et la classe AsterStarPress sont abstraites. Chez Oozinoz, vous devez crer une sous-classe qui implmente la mthode requise et charger ce code dans lordinateur de la presse. Vous pouvez implmenter markMoldIncomplete() en passant les informations concernant le moule incomplet au singleton MaterialManager qui garde trace de ltat matriel. Exercice 21.2 Ecrivez le code de la mthode markMoldIncomplete() de la classe OzAsterStarPress:
public class OzAsterStarPress extends AsterStarPress { public void markMoldIncomplete(int id) { // Exercice ! } }

Les concepteurs de la presse toiles Aster Star connaissent parfaitement le fonctionnement des units de production pyrotechniques et ont implment la communication avec lunit aux points de traitement appropris. Il se peut nanmoins que vous ayez besoin dtablir la communication un point que ces dveloppeurs ont omis.

218

Partie IV

Patterns dopration

Hooks
Un hook est un appel de mthode plac par un dveloppeur un point spcique dune procdure pour permettre dautres dveloppeurs dy insrer du code. Lorsque vous adaptez le code dun autre dveloppeur et avez besoin de disposer dun contrle un certain point auquel vous navez pas accs, vous pouvez demander un hook. Un dveloppeur serviable insrera un appel de mthode au niveau de ce point et fournira aussi gnralement une version stub de la mthode hook pour viter dautres clients de devoir la remplacer. Considrez la presse Aster qui dcharge sa pte chimique et rince abondamment sa zone de travail lorsquelle sarrte. Elle doit dcharger la pte pour empcher que celle-ci ne sche et bloque la machine. Chez Oozinoz, vous rcuprez la pte et la dcoupez en ds qui serviront de petites toiles dans des chandelles romaines (une chandelle romaine est un tube stationnaire qui contient un mlange de charges explosives et dtoiles). Une fois la pte dcharge, vous faites en sorte quun robot la place sur un tapis spar, comme illustr Figure 21.2. Il importe de procder au dchargement avant que la machine ne lave sa zone de travail. Le problme est que vous voulez prendre le contrle entre les deux instructions de la mthode shutdown():
dischargePaste(); flush();

Vous pourriez remplacer dischargePaste() par une mthode qui ajoute un appel pour collecter la pte :
public void dischargePaste() { super.dischargePaste(); getFactory().collectPaste(); }

Cette mthode insre une tape aprs le dchargement de la pte. Cette tape utilise un singleton Factory pour collecter la pte. Lorsque la mthode shutdown() sexcutera, le robot recueillera la pte dcharge avant que la presse ne soit rince. Malheureusement, le code de dischargePaste() introduit un risque. Les dveloppeurs de chez Aster ne sauront certainement pas que vous avez dni ainsi cette mthode. Sils modient leur code pour dcharger la pte un moment o vous ne voulez pas la collecter, une erreur surviendra.

Chapitre 21

TEMPLATE METHOD

219

Les dveloppeurs cherchent gnralement rsoudre les problmes en crivant du code. Mais ici, il sagit de rsoudre un problme potentiel en communiquant les uns avec les autres. Exercice 21.3 Rdigez une note lattention des dveloppeurs de chez Aster leur demandant dintroduire un changement qui vous permettra de collecter la pte dcharge en toute scurit avant que la machine ne rince sa zone de travail.

Ltape fournie par une sous-classe dans TEMPLATE METHOD peut tre ncessaire pour complter lalgorithme ou peut reprsenter une tape optionnelle qui sinsre dans le code dune sous-classe, souvent la demande dun autre dveloppeur. Bien que lobjectif de ce pattern soit de laisser une classe spare dnir une partie dun algorithme, vous pouvez aussi lappliquer lorsque vous refactorisez un algorithme apparaissant dans plusieurs mthodes.

Refactorisation pour appliquer TEMPLATE METHOD


Lorsque TEMPLATE METHOD est appliqu, vous trouverez des hirarchies de classes o une super-classe fournit la structure gnrale dun algorithme et o des sousclasses en fournissent certaines tapes. Vous pouvez adopter cette approche, refactorisant en vue dappliquer TEMPLATE METHOD, lorsque vous trouvez des algorithmes similaires dans des mthodes diffrentes (refactoriser consiste transformer des programmes en des programmes quivalents mais mieux conus). Considrez les hirarchies parallles Machine et MachinePlanner introduites au Chapitre 16, consacr au pattern FACTORY METHOD. Comme le montre la Figure 21.4, la classe Machine fournit une mthode createPlanner() en tant que FACTORY METHOD qui retourne une sous-classe approprie de MachinePlanner. Deux des sous-classes de Machine instancient des sous-classes spciques de la hirarchie MachinePlanner lorsquil leur est demand de crer un planicateur. Ces classes, ShellAssembler et StarPress, posent un mme problme en ce quelles ne peuvent crer un MachinePlanner qu la demande.

220

Partie IV

Patterns dopration

Figure 21.4
Un objet Machine peut crer une instance approprie de MachinePlanner pour lui-mme.
MachinePlanner

Machine

MachinePlanner( m:Machine)

createPlanner(): MachinePlanner

BasicPlanner

ShellAssembler

ShellPlanner

createPlanner(): MachinePlanner

StarPress

StarPressPlanner

createPlanner(): MachinePlanner

Fuser

Mixer

Si vous examinez le code de ces classes, vous noterez que les sous-classes emploient des techniques similaires pour procder une initialisation paresseuse (lazy-initialization) dun planicateur. Par exemple, la classe ShellAssembler possde une mthode getPlanner() qui initialise un membre planner:
public ShellPlanner getPlanner() { if (planner == null) planner = new ShellPlanner(this); return planner; }

Chapitre 21

TEMPLATE METHOD

221

Dans la classe ShellPlanner, planner est de type ShellPlanner. La classe StarPress comprend aussi un membre planner mais le dclare comme tant de type StarPressPlanner. La mthode getPlanner() de la classe StarPress opre aussi une initialisation paresseuse de lattribut planner:
public StarPressPlanner getPlanner() { if (planner == null) planner = new StarPressPlanner(this); return planner; }

Les autres sous-classes de Machine adoptent une approche analogue pour crer un planicateur seulement lorsquil est ncessaire. Cela prsente une opportunit de refactorisation, vous permettant de nettoyer et de rduire votre code. En supposant que vous dcidiez dajouter la classe Machine un attribut planner de type MachinePlanner, cela vous permettrait de supprimer cet attribut des sous-classes et dliminer les mthodes getPlanner() existantes. Exercice 21.4 Ecrivez le code de la mthode getPlanner() de la classe Machine.

Vous pouvez souvent refactoriser votre code en une instance de TEMPLATE METHOD en rendant abstraite la structure gnrale de mthodes qui se ressemblent, cest-dire en plaant cette structure dans une super-classe et en laissant aux sous-classes le soin de fournir ltape qui diffre dans leur implmentation de lalgorithme.

Rsum
Lobjectif de TEMPLATE METHOD est de dnir un algorithme dans une mthode, laissant certaines tapes abstraites, non dnies, ou dnies dans une interface, de sorte que dautres classes puissent se charger de les implmenter. Ce pattern fonctionne comme un contrat entre les dveloppeurs. Un dveloppeur fournit la structure gnrale dun algorithme, et un autre en fournit une certaine tape. Il peut sagir dune tape qui complte lalgorithme ou qui sert de hook vous permettant dinsrer votre code des points spciques de la procdure.

222

Partie IV

Patterns dopration

TEMPLATE METHOD nimplique pas que vous deviez crire la mthode modle avant les sous-classes dimplmentation. Il peut arriver que vous tombiez sur des mthodes similaires dans une hirarchie existante. Vous pourriez alors en extraire la structure gnrale dun algorithme et la placer dans une super-classe, appliquant ce pattern pour simplier et rorganiser votre code.

22
STATE
Ltat dun objet est une combinaison des valeurs courantes de ses attributs. Lorsque vous appelez une mthode set dun objet ou assignez une valeur lun de ses champs, vous changez son tat. Les objets modient souvent aussi eux-mmes leur tat lorsque leurs mthodes sexcutent. Le terme tat (state) est parfois employ pour dsigner un attribut changeant dun objet. Par exemple, nous pourrions dire quune machine est dans un tat actif ou inactif. Dans un tel cas, la partie changeante de ltat de lobjet est laspect le plus important de son comportement. En consquence, la logique qui dpend de ltat de lobjet peut se trouver rpartie dans de nombreuses mthodes de la classe. Une logique semblable ou identique peut ainsi apparatre de nombreuses fois, augmentant le travail de maintenance du code. Une faon dviter cet parpillement de la logique dpendant de ltat dun objet est dintroduire un nouveau groupe de classes, chacune reprsentant un tat diffrent. Il faut ensuite placer le comportement spcique un tat dans la classe approprie. Lobjectif du pattern STATE est de distribuer la logique dpendant de ltat dun objet travers plusieurs classes qui reprsentent chacune un tat diffrent.

Modlisation dtats
Lorsque vous modlisez un objet dont ltat est important, il se peut quil dispose dune variable qui garde trace de la faon dont il devrait se comporter, selon son tat. Cette variable apparat peut-tre dans des instructions if en cascade complexes qui se concentrent sur la faon de ragir aux vnements expriments par lobjet.

224

Partie IV

Patterns dopration

Cette approche de modlisation de ltat prsente deux problmes. Premirement, les instructions if peuvent devenir complexes ; deuximement, lorsque vous ajustez la faon dont vous modlisez ltat, il est souvent ncessaire dajuster les instructions if de plusieurs mthodes. Le pattern STATE offre une approche plus claire et plus simple, utilisant une opration distribue. Il vous permet de modliser des tats en tant quobjets, encapsulant la logique dpendant de ltat dans des classes distinctes. Avant de voir ce pattern luvre, il peut tre utile dexaminer un systme qui modlise des tats sans y recourir. Dans la section suivante, nous refactoriserons ce code pour dterminer si STATE peut amliorer la conception. Considrez le logiciel dOozinoz qui modlise les tats dune porte de carrousel. Un carrousel est un grand rack intelligent qui accepte des produits par une porte et les stocke daprs leur code-barres. La porte fonctionne au moyen dun seul bouton. Lorsquelle est ferme, toucher le bouton provoque son ouverture. Le toucher avant quelle soit compltement ouverte la fait se fermer. Lorsquelle est compltement ouverte, elle commence se fermer automatiquement aprs deux secondes. Vous pouvez empcher cela en touchant le bouton pendant que la porte est encore ouverte. La Figure 22.1 montre les tats et les transitions de cette porte. Le code correspondant est prsent un peu plus loin.

touch

Closed

complete

touch

Opening touch complete timeout

Closing

touch Open touch StayOpen

Figure 22.1
La porte du carrousel dispose dun bouton de contrle ragissant au toucher et permettant de changer ses tats.

Chapitre 22

STATE

225

Ce diagramme est une machine tats UML. De tels diagrammes peuvent tre beaucoup plus informatifs quune description textuelle. Exercice 22.1 Supposez que vous ouvriez la porte et passiez une caisse de produits de lautre ct. Y a-t-il un moyen de faire en sorte que la porte commence se fermer avant le dlai de deux secondes ?

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.


Vous pourriez introduire dans le logiciel du carrousel un objet Door quil actualiserait avec les changements dtat de la porte. La Figure 22.2 prsente la classe Door.
Figure 22.2
La classe Door modlise une porte de carrousel, sappuyant sur les vnements de changement dtat envoys par le carrousel.
Observable

Door

complete() setState(state:int) status():String timeout() touch()

La classe Door est Observable de sorte que les clients, telle une interface GUI, puissent afcher ltat de la porte. La dnition de cette classe tablit les diffrents tats que peut prendre la porte :
package com.oozinoz.carousel; import java.util.Observable;

226

Partie IV

Patterns dopration

public class Door extends Observable { public final int CLOSED = -1; public final int OPENING = -2; public final int OPEN = -3; public final int CLOSING = -4; public final int STAYOPEN = -5; private int state = CLOSED; // ... }

(Vous pourriez choisir dutiliser un type numr si vous programmez avec Java 5.) La description textuelle de ltat de la porte dpendra videmment de ltat dans lequel elle se trouve :
public String status() { switch (state) { case OPENING: return "Opening"; case OPEN: return "Open"; case CLOSING: return "Closing"; case STAYOPEN: return "StayOpen"; default: return "Closed"; } }

Lorsquun utilisateur touche le bouton du carrousel, ce dernier gnre un appel de la mthode touch() dun objet Door. Le code de Door pour une transition dtat rete les informations de la Figure 22.1 :
public void touch() { switch (state) { case OPENING: case STAYOPEN: setState(CLOSING); break; case CLOSING: case CLOSED: setState(OPENING); break;

Chapitre 22

STATE

227

case OPEN: setState(STAYOPEN); break; default: throw new Error("Ne peut se produire"); } }

La mthode setState() de la classe Door notie aux observateurs le changement dtat de la porte :
private void setState(int state) { this.state = state; setChanged(); notifyObservers(); }

Exercice 22.2 Ecrivez le code des mthodes complete() et timeout() de la classe Door.

Refactorisation pour appliquer STATE


Le code de Door est quelque peu complexe car lutilisation de la variable state est rpartie travers toute la classe. En outre, il peut tre difcile de comparer les mthodes de transition dtat, plus particulirement touch(), avec la machine tats de la Figure 22.1. Le pattern STATE peut vous aider simplier ce code. Pour lappliquer dans cet exemple, il faut dnir chaque tat de la porte en tant que classe distincte, comme illustr Figure 22.3. La refactorisation illustre dans cette gure cre une classe spare pour chaque tat dans lequel la porte peut se trouver. Chacune delles contient la logique permettant de rpondre au toucher du bouton pendant que la porte est dans un certain tat. Par exemple, le chier DoorClosed.java contient le code suivant :
package com.oozinoz.carousel; public class DoorClosed extends DoorState { public DoorClosed(Door2 door) { super(door); } public void touch() { door.setState(door.OPENING); } }

228

Partie IV

Patterns dopration

Door2

DoorState

complete() setState( state:DoorState) status() timeout() touch()

DoorState(d:Door2) complete() status() timeout() touch()

DoorClosed DoorOpening touch() ... touch() ... touch() ... DoorClosing

DoorOpen

DoorStayOpen

touch() ...

touch() ...

Figure 22.3
Le diagramme reprsente les tats de la porte en tant que classes dans une structure qui rete la machine tats de la porte.

La mthode touch() de la classe DoorClosed informe lobjet Door2 du nouvel tat de la porte. Cet objet est celui reu par le constructeur de DoorClosed. Cette approche implique que chaque objet tat contienne une rfrence un objet Door2 pour pouvoir informer la porte des transitions dtat. Un objet tat ne peut donc sappliquer ici qu une seule porte. La prochaine section dcrit comment modier cette conception pour quun mme ensemble dtats sufse pour un nombre quelconque de portes.

Chapitre 22

STATE

229

La gnration dun objet Door2 doit saccompagner de la cration dune suite dtats appartenant la porte :
package com.oozinoz.carousel; import java.util.Observable; public class Door2 extends Observable { public final DoorState CLOSED = new DoorClosed(this); public final DoorState CLOSING = new DoorClosing(this); public final DoorState OPEN = new DoorOpen(this); public final DoorState OPENING = new DoorOpening(this); public final DoorState STAYOPEN = new DoorStayOpen(this); private DoorState state = CLOSED; // ... }

La classe abstraite DoorState requiert des sous-classes pour implmenter touch(). Cela est cohrent avec la machine tats, dans laquelle tous les tats possdent une transition touch(). Cette classe ne dnit pas les autres transitions, les laissant stub, pour permettre aux sous-classes de les remplacer ou dignorer les messages non pertinents :
package com.oozinoz.carousel; public abstract class DoorState { protected Door2 door; public abstract void touch(); public void complete() { } public void timeout() { } public String status() { String s = getClass().getName(); return s.substring(s.lastIndexOf(.) + 1); } public DoorState(Door2 door) { this.door = door; } }

Notez que la mthode status() fonctionne pour tous les tats et est beaucoup plus simple quavant la refactorisation.

230

Partie IV

Patterns dopration

La nouvelle conception ne change pas le rle dun objet Door2 en ce quil reoit toujours les changements dtat de la part du carrousel, mais maintenant il les passe simplement son objet state courant :
package com.oozinoz.carousel; import java.util.Observable; public class Door2 extends Observable { // variables et constructeur... public void touch() { state.touch(); } public void complete() { state.complete(); } public void timeout() { state.timeout(); } public String status() { return state.status(); } protected void setState(DoorState state) { this.state = state; setChanged(); notifyObservers(); } }

Les mthodes touch(), complete(), timeout() et status() illustrent le rle du polymorphisme dans cette conception. Chacune delles est toujours un genre dinstruction switch. Dans chaque cas, lopration est xe, mais la classe du rcepteur, cest--dire la classe de state, varie quant elle. La rgle du polymorphisme est que la mthode qui sexcute dpend de la signature de lopration et de la classe du rcepteur. Que se passe-t-il lorsque vous appelez touch()? Cela dpend de ltat de la porte. Le code accomplit toujours un "switch", mais, en sappuyant sur le polymorphisme, il est plus simple quauparavant. La mthode setState() de la classe Door2 est maintenant utilise par des sousclasses de DoorState. Celles-ci ressemblent leurs contreparties dans la machine tats de la Figure 22.1. Par exemple, le code de DoorOpen gre les appels de touch() et timeout(), les deux transitions de ltat Open dans la machine :
package com.oozinoz.carousel; public class DoorOpen extends DoorState {

Chapitre 22

STATE

231

public DoorOpen(Door2 door) { super(door); } public void touch() { door.setState(door.STAYOPEN); } public void timeout() { door.setState(door.CLOSING); } }

Exercice 22.3 Ecrivez le code de DoorClosing.java.

La nouvelle conception donne lieu un code beaucoup plus simple, mais il se peut que vous ne soyez pas compltement satisfait du fait que les "constantes" utilises par la classe Door soient en fait des variables locales.

Etats constants
Le pattern STATE rpartit la logique dpendant de ltat dun objet dans plusieurs classes qui reprsentent les diffrents tats de lobjet. Il ne spcie toutefois pas comment grer la communication et les dpendances entre les objets tat et lobjet central auquel ils sappliquent. Dans la conception prcdente, chaque classe dtat acceptait un objet Door dans son constructeur. Les objets tat conservaient cet objet et sen servaient pour actualiser ltat de la porte. Cette approche nest pas forcment mauvaise, mais elle a pour effet quinstancier un objet Door entrane linstanciation dun ensemble complet dobjets DoorState. Vous pourriez prfrer une conception qui cre un seul ensemble statique dobjets DoorState et require que la classe Door gre toutes les actualisations rsultant des changements dtat. Une faon de rendre les objets tat constants est de faire en sorte que les classes dtat identient simplement ltat suivant, laissant le soin la classe Door dactualiser sa variable state. Dans une telle conception, la mthode touch() de la classe Door, par exemple, actualise la variable state comme suit :
public void touch() { state = state.touch(); }

232

Partie IV

Patterns dopration

Notez que le type de retour de la mthode touch() de la classe Door est void. Les sous-classes de DoorState implmenteront aussi touch() mais retourneront une valeur DoorState. Par exemple, voici prsent le code de la mthode touch() de DoorOpen:
public DoorState touch() { return DoorState.STAYOPEN; }

Dans cette conception, les objets DoorState ne conservent pas de rfrence vers un objet Door, aussi lapplication requiert-elle une seule instance de chaque objet DoorState. Une autre approche pour rendre les objets DoorState constants est de faire passer lobjet Door central pendant les transitions dtat. Pour cela, il faut ajouter un paramtre Door aux mthodes complete(), timeout() et touch(). Elles recevront alors lobjet Door en tant que paramtre et actualiseront son tat sans conserver de rfrence vers lui. Exercice 22.4 Compltez le diagramme de classes de la Figure 22.4 pour reprsenter une conception o les objets DoorState sont constants et qui fait passer un objet Door pendant les transitions dtat.
Figure 22.4
Une fois complt, ce diagramme reprsentera une conception qui rend les tats de la porte constants.
Door DoorState CLOSED:DoorClosed

Chapitre 22

STATE

233

Lorsque vous appliquez le pattern STATE, vous disposez dune libert totale dans la faon dont votre conception organise la communication des changements dtat. Les classes dtat peuvent conserver une rfrence lobjet central dont ltat est modlis. Sinon, vous pouvez faire passer cet objet durant les transitions. Vous pouvez aussi faire en sorte que les sous-classes soient de simples fournisseurs dinformations dterminant ltat suivant mais nactualisant pas lobjet central. Lapproche que vous choisissez dpend du contexte de votre application ou de considrations esthtiques. Si vos tats sont utiliss par diffrents threads, assurez-vous que vos mthodes de transition sont synchronises pour garantir labsence de conit lorsque deux threads tentent de modier ltat au mme moment. La puissance du pattern STATE est de permettre la centralisation de la logique de diffrents tats dans une mme classe.

Rsum
De manire gnrale, ltat dun objet dpend de la valeur collective de ses variables dinstance. Dans certains cas, la plupart des attributs de lobjet sont assez statiques une fois dnis, lexception dun attribut qui est dynamique et joue un rle prdominant dans la logique de la classe. Cet attribut peut reprsenter ltat de lobjet tout entier et peut mme tre nomm state. Une variable dtat dominante peut exister lorsquun objet modlise une entit du monde rel dont ltat est important, telle quune transaction ou une machine. La logique qui dpend de ltat de lobjet peut alors apparatre dans de nombreuses mthodes. Vous pouvez simplier un tel code en plaant les comportements spciques aux diffrents tats dans une hirarchie dobjets tat. Chaque classe dtat peut ainsi contenir le comportement pour un seul tat du domaine. Cela permet galement dtablir une correspondance directe entre les classes dtat et les tats dune machine tats. Pour grer les transitions entre les tats, vous pouvez laisser lobjet central conserver des rfrences vers un ensemble dtats. Ou bien, dans les mthodes de transition dtat, vous pouvez faire passer lobjet central dont ltat change. Vous pouvez sinon faire des classes dtat des fournisseurs dinformations qui indiquent simplement un tat subsquent sans actualiser lobjet central. Quelle que soit la manire dont vous procdez, le pattern STATE simplie votre code en distribuant une opration travers une collection de classes qui reprsentent les diffrents tats dun objet.

23
STRATEGY
Une stratgie est un plan, ou une approche, pour atteindre un but en fonction de certaines conditions initiales. Elle est donc semblable un algorithme, lequel est une procdure gnrant un rsultat partir dun ensemble dentres. Habituellement, une stratgie dispose dune plus grande latitude quun algorithme pour accomplir son objectif. Cette latitude signie galement que les stratgies apparaissent souvent par groupes, ou familles, dalternatives. Lorsque plusieurs stratgies apparaissent dans un programme, le code peut devenir complexe. La logique qui entoure les stratgies doit slectionner lune delles, et ce code de slection peut lui-mme devenir complexe. Lexcution de plusieurs stratgies peut emprunter diffrents chemins dans le code, lequel peut mme tre contenu dans une seule mthode. Lorsque la slection et lexcution de diverses mthodes conduisent un code complexe, vous pouvez appliquer le pattern STRATEGY pour le simplier. Lopration stratgique dnit les entres et les sorties dune stratgie mais laisse limplmentation aux classes individuelles. Les classes qui implmentent les diverses approches implmentent la mme opration et sont donc interchangeables, prsentant des stratgies diffrentes mais la mme interface aux clients. Le pattern STRATEGY permet une famille de stratgies de coexister sans que leurs codes respectifs sentremlent. Il spare aussi la logique de slection dune stratgie des stratgies elles-mmes. Lobjectif du pattern STRATEGY est dencapsuler des approches, ou stratgies, alternatives dans des classes distinctes qui implmentent chacune une opration commune.

236

Partie IV

Patterns dopration

Modlisation de stratgies
Le pattern STRATEGY aide organiser et simplier le code en encapsulant diffrentes approches dun problme dans plusieurs classes. Avant de le voir luvre, il peut tre utile dexaminer un programme qui modlise des stratgies sans lappliquer. Dans la section suivante, nous refactoriserons ce code en appliquant STRATEGY pour amliorer sa qualit. Considrez la politique publicitaire dOozinoz qui suggre aux clients qui visitent son site Web ou prennent contact avec son centre dappels quel artice acheter. Oozinoz utilise deux moteurs de recommandation du commerce pour dterminer larticle appropri proposer. La classe Customer choisit et applique un des deux moteurs.
LikeMyStuff Customer suggest(:Customer):Object isRegistered():bool getRecommended():Firework spendingSince(d:Date): double Rel8

advise(:Customer):Object Firework

... getRandom():Firework lookup(name:String): Firework

Figure 23.1
La classe Customer sappuie sur dautres classes pour ses recommandations, parmi lesquelles deux moteurs de recommandation du commerce.

Un des moteurs de recommandation, Rel8, suggre un achat en se fondant sur les similitudes du client avec dautres clients. Pour que cela fonctionne, le client doit stre enregistr au pralable et avoir fourni des informations sur ses prfrences en matire dartices et autres distractions. Sil nest pas encore enregistr, Oozinoz

Chapitre 23

STRATEGY

237

utilise lautre moteur, LikeMyStuff, qui suggre un achat sur la base des achats rcents du client. Si aucun des deux moteurs ne dispose de sufsamment de donnes pour assurer sa fonction, le logiciel de publicit choisit un artice au hasard. Une promotion spciale peut nanmoins avoir la priorit sur toutes ces considrations, mettant en avant un artice particulier quOozinoz cherche vendre. La Figure 23.1 prsente les classes qui collaborent pour suggrer un artice un client. Les moteurs LikeMyStuff et Rel8 acceptent un objet Customer et suggrent quel artice proposer au client. Tous deux sont congurs chez Oozinoz pour grer des artices, mais LikeMyStuff requiert une base de donnes tandis que Rel8 travaille essentiellement partir dun modle objet. Le code de la mthode getRecommended() de la classe Customer rete la politique publicitaire dOozinoz :
public Firework getRecommended() { // En cas de promotion dun artifice particulier, le retourner. try { Properties p = new Properties(); p.load(ClassLoader.getSystemResourceAsStream( "config/strategy.dat")); String promotedName = p.getProperty("promote"); if (promotedName != null) { Firework f = Firework.lookup(promotedName); if (f != null) return f; } } catch (Exception ignored) { // Si la ressource est manquante ou na pas t charge, // se rabattre sur lapproche suivante. } // Si le client est enregistr, le comparer aux autres clients. if (isRegistered()) { return (Firework) Rel8.advise(this); } // Vrifier les achats du client sur lanne coule. Calendar cal = Calendar.getInstance(); cal.add(Calendar.YEAR, -1); if (spendingSince(cal.getTime()) > 1000) return (Firework) LikeMyStuff.suggest(this); // Retourner nimporte quel artifice. return Firework.getRandom(); }

238

Partie IV

Patterns dopration

Ce code est extrait du package com.oozinoz.recommendation de la base de code Oozinoz accessible ladresse www.oozinoz.com. La mthode getRecommended() sattend ce que, sil y a une promotion, elle soit nomme dans un chier strategy.dat dans un rpertoire config. Voici quoi ressemblerait un tel chier :
promote=JSquirrel

En labsence de ce chier, la mthode utilise le moteur Rel8 si le client est inscrit. Si le client nest pas inscrit, elle utilise le moteur LikeMyStuff lorsque le client a dj dpens un certain montant au cours de lanne passe. Si aucune meilleure recommandation nest possible, le code slectionne et propose un artice quelconque. Cette mthode fonctionne, et vous avez probablement dj vu pire comme code, mais nous pouvons certainement lamliorer.

Refactorisation pour appliquer STRATEGY


La mthode getRecommended() prsente plusieurs problmes. Dabord, elle est longue, au point que des commentaires doivent expliquer ses diffrentes parties. Les mthodes courtes sont plus faciles comprendre et ont rarement besoin dtre expliques, elles sont donc gnralement prfrables aux mthodes longues. Ensuite, non seulement elle choisit une stratgie mais elle lexcute galement, ce qui constitue deux fonctions distinctes qui peuvent tre spares. Vous pouvez simplier ce code en appliquant STRATEGY. Pour cela, vous devez :
m m m

crer une interface qui dnit lopration stratgique ; implmenter linterface avec des classes qui reprsentent chacune une stratgie ; refactoriser le code pour slectionner et utiliser une instance de la classe d e stratgie approprie.

Supposez que vous criez une interface Advisor, comme illustr Figure 23.2.
Figure 23.2
Linterface Advisor dnit une opration que diverses classes peuvent implmenter avec diffrentes stratgies.
interface Advisor

recommend(c:Customer):Firework

Chapitre 23

STRATEGY

239

Linterface Advisor dclare quune classe qui limplmente peut accepter un client et recommander un artice. Ltape suivante consiste refactoriser la mthode getRecommended() de la classe Customer pour crer des classes reprsentant chacune des stratgies de recommandation. Chaque classe fournit une implmentation diffrente de la mthode recommend() spcie par linterface Advisor.

Customer BIG_SPENDER_DOLLARS:int getAdvisor():Advisor isRegistered():boolean isBigSpender():boolean getRecommended():Firework spendingSince(d:Date): double

interface Advisor

recommend(c:Customer):Firework

GroupAdvisor

ItemAdvisor

PromotionAdvisor

RandomAdvisor

Figure 23.3
Compltez ce diagramme pour montrer la refactorisation du logiciel de recommandation, avec les stratgies apparaissant comme implmentations dune interface commune.

Une fois que vous disposez des classes de stratgie, vous devez y placer le code de la mthode getRecommended() de la classe Customer. Les deux classes les plus simples sont GroupAdvisor et ItemAdvisor. Elles doivent seulement envelopper les appels pour les moteurs de recommandation. Une interface ne pouvant dnir que des mthodes dinstance, GroupAdvisor et ItemAdvisor doivent tre instancies pour supporter linterface Advisor. Comme un seul objet de chaque classe est ncessaire, Customer devrait inclure une seule instance statique de chaque classe.

240

Partie IV

Patterns dopration

La Figure 23.4 illustre une conception pour ces classes.

interface Advisor

recommend(c:Customer): Firework

GroupAdvisor

Rel8

recommend(c:Customer): Firework

advise(c:Customer):Object

ItemAdvisor

LikeMyStuff

recommend(c:Customer): Firework

suggest(c:Customer):Object

Figure 23.4
Les implmentations de linterface Advisor fournissent lopration stratgique recommend(), sappuyant sur les moteurs de recommandation.

Les classes Advisor traduisent les appels de recommend() en interfaces requises par les moteurs sous-jacents. Par exemple, la classe GroupAdvisor traduit ces appels en linterface advise() requise par le moteur Rel8:
public Firework recommend(Customer c) { return (Firework) Rel8.advise(c); }

Exercice 23.2 Outre le pattern STRATEGY, quel autre pattern apparat dans les classes GroupAdvisor et ItemAdvisor?

Chapitre 23

STRATEGY

241

Les classes GroupAdvisor et ItemAdvisor oprent en traduisant un appel de la mthode recommend() en un appel dun moteur de recommandation. Il faut aussi crer une classe PromotionAdvisor et une classe RandomAdvisor, en refactorisant le code de la mthode getRecommended() de Customer. A linstar de GroupAdvisor et ItemAdvisor, ces classes fournissent aussi lopration recommend(). Le constructeur de PromotionAdvisor devrait dterminer sil existe une promotion en cours. Vous pourriez ensuite ajouter cette classe une mthode hasItem() indiquant sil y a un article en promotion :
public class PromotionAdvisor implements Advisor { private Firework promoted; public PromotionAdvisor() { try { Properties p = new Properties(); p.load(ClassLoader.getSystemResourceAsStream( "config/strategy.dat")); String promotedFireworkName = p.getProperty("promote"); if (promotedFireworkName != null) promoted = Firework.lookup(promotedFireworkName); } catch (Exception ignored) { // Ressource introuvable ou non charge promoted = null; } } public boolean hasItem() { return promoted != null; } public Firework recommend(Customer c) { return promoted; } }

La classe RandomAdvisor est simple :


public class RandomAdvisor implements Advisor { public Firework recommend(Customer c) { return Firework.getRandom(); } }

La refactorisation de Customer permet de sparer la slection dune stratgie de son utilisation. Un attribut advisor dun objet Customer contient le choix courant de la stratgie appliquer. La classe Customer2 refactorise procde une

242

Partie IV

Patterns dopration

initialisation paresseuse de cet attribut avec une logique qui rete la politique publicitaire dOozinoz :
private Advisor getAdvisor() { if (advisor == null) { if (promotionAdvisor.hasItem()) advisor = promotionAdvisor; else if (isRegistered()) advisor = groupAdvisor; else if (isBigSpender()) advisor = itemAdvisor; else advisor = randomAdvisor; } return advisor; }

Exercice 23.3 Ecrivez le nouveau code de la mthode Customer.getRecommended().

Comparaison de STRATEGY et STATE


Le code refactoris consiste presque entirement en des mthodes simples dans des classes simples. Cela reprsente un avantage en soi et facilite lajout de nouvelles stratgies. La refactorisation se fonde principalement sur le principe de distribuer une opration travers un groupe de classes associes. A cet gard, STRATEGY est identique STATE. En fait, certains dveloppeurs se demandent mme si ces deux patterns sont vraiment diffrents. Dun ct, la diffrence entre modliser des tats et modliser des stratgies peut paratre subtile. En effet, STATE et STRATEGY semblent faire une utilisation du polymorphisme quasiment identique sur le plan structurel. Dun autre ct, dans le monde rel, les stratgies et les tats reprsentent clairement des concepts diffrents, et cette diffrence donne lieu divers problmes de modlisation. Par exemple, les transitions sont importantes lorsquil sagit de modliser des tats tandis quelles sont hors de propos lorsquil sagit de choisir une stratgie. Une autre diffrence est que STRATEGY peut permettre un client de slectionner ou de fournir une stratgie, une ide qui sapplique rarement STATE. Etant donn que ces deux patterns nont pas le mme objectif, nous continuerons de

Chapitre 23

STRATEGY

243

les considrer comme diffrents. Mais vous devez savoir que tout le monde ne reconnat pas cette distinction.

Comparaison de STRATEGY et TEMPLATE METHOD


Le Chapitre 21, consacr TEMPLATE METHOD, a pris le triage comme exemple de TEMPLATE METHOD. Vous pouvez utiliser lalgorithme sort() de la classe Arrays ou Collection pour trier nimporte quelle liste dobjets, ds lors que vous fournissez une tape pour comparer deux objets. Vous pourriez avancer que lorsque vous fournissez une tape de comparaison pour un algorithme de tri, vous changez la stratgie. En supposant par exemple que vous vendiez des fuses, le fait de les prsenter tries par prix ou tries par pousses reprsente deux stratgies marketing diffrentes. Exercice 23.4 Expliquez en quoi la mthode Arrays.sort() constitue un exemple de TEMPLATE METHOD et/ou de STRATEGY.

Rsum
Il arrive que la logique qui modlise des stratgies alternatives apparaisse dans une seule classe, souvent mme dans une seule mthode. De telles mthodes tendent tre trop compliques et mler la logique de slection dune stratgie avec son excution. Pour simplier votre code, vous pouvez crer un groupe de classes, une pour chaque stratgie, puis dnir une opration et la distribuer travers ces classes. Chaque classe peut ainsi encapsuler une stratgie, rduisant considrablement le code. Vous devez aussi permettre au client qui utilise une stratgie den slectionner une. Ce code de slection peut tre complexe mme lissue de la refactorisation, mais vous devriez pouvoir le rduire jusqu ce quil ressemble presque du pseudo-code dcrivant la slection dune stratgie dans le domaine du problme. Typiquement, un client conserve la stratgie slectionne dans une variable contextuelle. Lexcution de la stratgie revient ainsi simplement transmettre au contexte lappel de lopration stratgique, en utilisant le polymorphisme pour excuter la stratgie approprie. En encapsulant les stratgies alternatives dans des classes spares implmentant chacune une opration commune, le pattern STRATEGY permet de crer un code clair et simple qui modlise une famille dapproches pour rsoudre un problme.

24
COMMAND
Le moyen classique de dclencher lexcution dune mthode est de lappeler. Il arrive souvent nanmoins que vous ne puissiez pas contrler le moment prcis ou le contexte de son excution. Dans de telles situations, vous pouvez lencapsuler dans un objet. En stockant les informations ncessaires linvocation dune mthode dans un objet, vous pouvez la passer en tant que paramtre, permettant ainsi un client ou un service de dterminer quand linvoquer. Lobjectif du pattern COMMAND est dencapsuler une requte dans un objet.

Un exemple classique : commandes de menus


Les kits doutils qui supportent des menus appliquent gnralement le pattern COMMAND. Chaque lment de menu saccompagne dun objet qui sait comment se comporter lorsque lutilisateur clique dessus. Cette conception permet de sparer la logique GUI de lapplication. La bibliothque Swing adopte cette approche, vous permettant dassocier un ActionListener chaque JMenuItem. Comment faire pour quune classe appelle une de vos mthodes lorsque lutilisateur clique ? Il faut pour cela recourir au polymorphisme, cest--dire rendre le nom de lopration xe et laisser limplmentation varier. Pour JMenuItem, lopration est actionPerformed(). Lorsque lutilisateur fait un choix, llment JMenuItem appelle la mthode actionPerformed() de lobjet qui sest enregistr en tant que listener.

246

Partie IV

Patterns dopration

Exercice 24.1 Le fonctionnement des menus Java facilite lapplication du pattern COMMAND mais ne vous demande pas dorganiser votre code en commandes. En fait, il est frquent de dvelopper une application dans laquelle un seul objet coute tous les vnements dune interface GUI. Quel pattern cela vous voque-t-il ?

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.


Lorsque vous dveloppez une application Swing, vous pouvez enregistrer un seul listener pour tous les vnement GUI, plus particulirement lorsque les composants GUI interagissent. Toutefois, pour les menus, il ne sagit gnralement pas de lapproche suivre. Si vous deviez utiliser un seul objet pour couter les menus, il devrait dterminer pour chaque vnement lobjet GUI qui la gnr. Au lieu de cela, lorsque vous avez plusieurs lments de menu qui donnent lieu des actions indpendantes, il peut tre prfrable dappliquer COMMAND. Lorsquun utilisateur slectionne un lment de menu, il invoque la mthode actionPerformed(). Lorsque vous crez llment, vous pouvez lui associer un ActionListener, avec une mthode actionPerformed() spcique au comportement de la commande. Plutt que de dnir une nouvelle classe pour implmenter ce petit comportement, il est courant demployer une classe anonyme. Considrez la classe Visualization2 du package com.oozinoz.visualization. Elle fournit une barre de menus avec un menu File (Fichier) qui permet lutilisateur denregistrer et de restaurer les visualisations dune unit de production Oozinoz simule. Ce menu comporte des lments Save As (Enregistrer sous) et Restore From (Restaurer partir de). Le code qui cre ces lments enregistre des listeners qui attendent la slection de lutilisateur. Ces listeners implmentent la mthode actionPerformed() en appelant les mthodes save() et load() de la classe Visualization2:
package com.oozinoz.visualization; import java.awt.event.*; import javax.swing.*; import com.oozinoz.ui.*; public class Visualization2 extends Visualization { public static void main(String[] args) { Visualization2 panel = new Visualization2(UI.NORMAL); JFrame frame = SwingFacade.launch(panel, "Operational Model");

Chapitre 24

COMMAND

247

frame.setJMenuBar(panel.menus()); frame.setVisible(true); } public Visualization2(UI ui) { super(ui); } public JMenuBar menus() { JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("File"); menuBar.add(menu); JMenuItem menuItem = new JMenuItem("Save As..."); menuItem.addActionListener(new ActionListener() { // Exercice ! }); menu.add(menuItem); menuItem = new JMenuItem("Restore From..."); menuItem.addActionListener(new ActionListener() { // Exercice ! }); menu.add(menuItem); return menuBar; } public void save() { /* omis */ } public void restore() { /* omis */ } }

Exercice 24.2 Compltez le code des sous-classes anonymes de ActionListener, en remplaant la mthode actionPerformed(). Notez que cette mthode attend un argument ActionEvent.

Lorsque vous associez des commandes un menu, vous les placez dans un contexte fourni par un autre dveloppeur : le framework de menus Java. Dans dautres utilisations de COMMAND, vous aurez le rle de dvelopper le contexte dans lequel les commandes sexcuteront. Par exemple, vous pourriez vouloir fournir un service de minutage qui enregistre la dure dexcution des mthodes.

248

Partie IV

Patterns dopration

Emploi de COMMAND pour fournir un service


Supposez que vous vouliez permettre aux dveloppeurs de connatre la dure dexcution dune mthode. Vous disposez dune interface Command dont voici lessence :
public abstract void execute();

Vous disposez galement de la classe CommandTimer suivante :


package com.oozinoz.utility; import com.oozinoz.robotInterpreter.Command; public class CommandTimer { public static long time(Command command) { long t1 = System.currentTimeMillis(); command.execute(); long t2 = System.currentTimeMillis(); return t2 - t1; } }

Vous pourriez tester la mthode time() au moyen dun test JUnit ressemblant ce qui suit. Notez que ce test nest pas exact car il peut chouer si le timer est irrgulier :
package app.command; import com.oozinoz.robotInterpreter.Command; import com.oozinoz.utility.CommandTimer; import junit.framework.TestCase; public class TestCommandTimer extends TestCase { public void testSleep() { Command doze = new Command() { public void execute() { try { Thread.sleep( 2000 + Math.round(10 * Math.random())); } catch (InterruptedException ignored) { } } }; long actual = // Exercice !

Chapitre 24

COMMAND

249

long expected = 2000; long delta = 5; assertTrue( "Devrait tre " + expected + " +/- " + delta + " ms", expected - delta <= actual && actual <= expected + delta); } }

Exercice 24.3 Compltez les instructions dassignation qui dnissent la valeur de actual de manire que la commande doze soit minute.

Hooks
Le Chapitre 21, consacr au pattern TEMPLATE METHOD, a introduit la presse toiles Aster, une machine intelligente qui inclut du code sappuyant sur ce pattern. Le code de cette machine vous permet de remplacer une mthode qui marque un moule comme tant incomplet sil est en cours de traitement au moment o elle est arrte. La classe AsterStarPress est abstraite, vous demandant de driver une sousclasse contenant une mthode markMoldIncomplete(). La mthode shutDown() de AsterStarPress utilise cette mthode pour garantir que lobjet du domaine sait que le moule est incomplet :
public void shutdown() { if (inProcess()) { stopProcessing(); markMoldIncomplete(currentMoldID); } usherInputMolds(); dischargePaste(); flush(); }

Il peut vous sembler peu commode dtendre AsterStarPress avec une classe que vous devez introduire dans lordinateur de bord de la presse. Supposez que vous demandiez aux dveloppeurs de chez Aster de fournir le hook sous une forme diffrente, en utilisant le pattern COMMAND. La Figure 24.1 illustre une commande Hook

250

Partie IV

Patterns dopration

que la classe AsterStarPress peut utiliser, vous permettant de paramtrer la presse en cours dexcution.

AsterStarPress

interface Hook

moldIncompleteHook:Hook getCurrentMoldID():int dischargePaste() flush() inProcess():boolean shutDown() stopProcessing() usherInputMolds() setMoldIncompleteHook(:Hook) execute(p:AsterStarPress) NullHook execute(p:AsterStarPress)

Figure 24.1
Une classe peut fournir un hook, cest--dire un moyen dinsrer du code personnalis, en invoquant une certaine commande un point prcis dans une procdure.

Dans la classe AsterStarPress originale, la mthode shutDown() sappuyait sur une tape devant tre dnie par des sous-classes. Dans la nouvelle conception, cette mthode utilise un hook pour excuter le code client aprs que le traitement a t interrompu mais avant la n du processus darrt :
public void shutDown() { if (inProcess()) { stopProcessing(); // Exercice ! } usherInputMolds(); dischargePaste(); flush(); }

Exercice 24.4 Compltez le code de la nouvelle mthode shutDown().

Chapitre 24

COMMAND

251

Cet exemple est reprsentatif dun autre pattern, NULL OBJECT [Woolf 1998], qui est seulement un peu moins connu que ceux dcrits dans Design Patterns. Ce pattern permet dviter davoir vrier lventualit dun pointeur null en introduisant un objet par dfaut qui est sans effet (voir Refactoring [Fowler et al. 1999] pour une explication de la faon dappliquer ce pattern votre code). Le pattern COMMAND reprsente une conception alternative TEMPLATE METHOD pour les hooks, et est semblable en termes dobjectif, ou de structure, plusieurs autres patterns.

COMMAND en relation avec dautres patterns


COMMAND ressemble au pattern INTERPRETER, ces deux patterns tant compars dans le prochain chapitre. Il ressemble aussi un pattern dans lequel un client sait quand une action est requise mais ne sait pas exactement quelle opration appeler. Exercice 24.5 Quel pattern convient dans la situation o un client sait quand crer un objet mais ne sait pas quelle classe instancier ?

Outre des similitudes avec dautres patterns, COMMAND collabore souvent galement avec dautres patterns. Par exemple, vous pourriez combiner COMMAND et MEDIATOR dans une conception MVC. Le Chapitre 19, consacr au pattern MEMENTO, en donne un exemple. La classe Visualization gre la logique de contrle GUI mais cone un mdiateur la logique relative au modle. Par exemple, cette classe utilise le code suivant pour effectuer une initialisation paresseuse de son bouton Undo :
protected JButton undoButton() { if (undoButton == null) { undoButton = ui.createButtonCancel(); undoButton.setText("Undo"); undoButton.setEnabled(false); undoButton.addActionListener(mediator.undoAction()); } return undoButton; }

Ce code applique COMMAND, introduisant une mthode undo() dans une instance de la classe ActionListener. Il applique galement MEDIATOR, laissant un objet central servir de mdiateur pour les vnements appartenant un modle objet sous-jacent.

252

Partie IV

Patterns dopration

Pour que la mthode undo() fonctionne, le code mdiateur doit restaurer une version antrieure de lunit de production simule, offrant lopportunit dappliquer un autre pattern qui accompagne souvent COMMAND. Exercice 24.6 Quel pattern permet dassurer le stockage et la restauration de ltat dun objet ?

Rsum
Le pattern COMMAND sert encapsuler une requte dans un objet, vous permettant de grer des appels de mthodes en tant quobjets, les passant et les invoquant lorsque le moment ou les conditions sont appropris. Un exemple classique de lintrt de ce pattern a trait aux menus. Les lments de menu savent quand excuter une action mais ne savent pas quelle mthode appeler. COMMAND permet de paramtrer un menu avec des appels de mthodes correspondant aux options quil contient. COMMAND peut aussi tre utilis pour permettre lexcution de code client dans le contexte dun service. Un service excute souvent du code avant et aprs linvocation du code client. Outre le fait de contrler le moment ou le contexte dexcution dune mthode, ce pattern peut offrir un mcanisme commode pour fournir des hooks, permettant un code client optionnel de sexcuter dans le cadre dun algorithme. Un autre aspect fondamental de COMMAND est quil entretient plusieurs relations intressantes avec dautres patterns. Il peut constituer une alternative TEMPLATE METHOD et peut aussi souvent collaborer avec MEDIATOR et MEMENTO.

25
INTERPRETER
A linstar du pattern COMMAND, le pattern INTERPRETER produit un objet excutable. Ces deux patterns diffrent en ce que INTERPRETER implique la cration dune hirarchie de classes dans laquelle chaque classe implmente, ou interprte, une opration commune de sorte quelle corresponde au nom de la classe. A cet gard, INTERPRETER est semblable aux patterns STATE et STRATEGY. Dans ces trois patterns, une opration commune apparat dans une collection de classes, chacune delles implmentant lopration de manire diffrente. Le pattern INTERPRETER ressemble aussi au pattern COMPOSITE, lequel dnit une interface commune pour des lments individuels ou des groupes dlments. COMPOSITE ne requiert pas des moyens diffrents de former des groupes, bien quil le permette. Par exemple, la hirarchie ProcessComponent du Chapitre 5, consacr au pattern COMPOSITE, autorise des squences et des alternances de ux de processus. Dans INTERPRETER, lide quil y ait diffrents types de compositions est essentielle (un INTERPRETER est souvent plac au-dessus dune structure COMPOSITE). La faon dont une classe compose dautres composants aide dnir comment une classe INTERPRETER implmente une opration. INTERPRETER nest pas un pattern facile comprendre. Vous pourriez avoir besoin de rviser le pattern COMPOSITE car nous nous y rfrerons dans ce chapitre. Lobjectif du pattern INTERPRETER est de vous permettre de composer des objets excutables daprs un ensemble de rgles de composition que vous dnissez.

254

Partie IV

Patterns dopration

Un exemple de INTERPRETER
Les robots quOozinoz utilise pour dplacer des produits dans une ligne de traitement comportent un interprteur qui contrle le robot mais dispose dun contrle limit sur les machines de la ligne. Vous pourriez voir les interprteurs comme se destinant aux langages de programmation, mais le cur du pattern INTERPRETER est constitu dune collection de classes qui permettent la composition dinstructions. Linterprteur des robots dOozinoz consiste en une hirarchie de classes qui encapsulent des commandes de robot. La hirarchie comporte une classe abstraite Command en son sommet et inclut tous les niveaux une opration execute(). La Figure 25.1 prsente la classe Robot et deux des commandes supportes par linterprteur.

com.oozinoz.robotInterpreter

Robot singleton:Robot Command carry( fromMachine:Machine, toMachine:Machine) * execute()

CarryCommand

CommandSequence

fromMachine:Machine toMachine:Machine CarryCommand( fromMachine:Machine, toMachine:Machine) execute() add(c:Command) execute()

Figure 25.1
Une hirarchie interprteur supporte la programmation lors de lexcution dun robot dusine.

Chapitre 25

INTERPRETER

255

Cette gure pourrait suggrer que le pattern COMMAND est prsent dans cette conception, avec une classe Command au sommet de la hirarchie. Toutefois, ce pattern a pour objectif dencapsuler une mthode dans un objet, ce que ne fait pas ici la hirarchie Command. Au lieu de cela, cette conception requiert que les sous-classes de Command rinterprtent lopration execute(), ce qui est lobjectif du pattern INTERPRETER: vous permettre de composer des objets excutables. Une hirarchie INTERPRETER typique inclurait plus de deux sous-classes et tendrait brivement la hirarchie Command. Les deux sous-classes de la Figure 25.1 sufsent nanmoins pour un exemple initial :
package app.interpreter; import com.oozinoz.machine.*; import com.oozinoz.robotInterpreter.*; public class ShowInterpreter { public static void main(String[] args) { MachineComposite dublin = OozinozFactory.dublin(); ShellAssembler assembler = (ShellAssembler) dublin.find("ShellAssembler:3302"); StarPress press = (StarPress) dublin.find("StarPress:3404"); Fuser fuser = (Fuser) dublin.find("Fuser:3102"); assembler.load(new Bin(11011)); press.load(new Bin(11015)); CarryCommand carry1 = new CarryCommand(assembler, fuser); CarryCommand carry2 = new CarryCommand(press, fuser); CommandSequence seq = new CommandSequence(); seq.add(carry1); seq.add(carry2); seq.execute(); } }

Ce code de dmonstration fait quun robot dusine dplace deux caisses de produits des machines oprationnelles vers un tampon de dchargement. Il fonctionne avec un composite de machines retourn par la mthode dublin() de la classe OozinozFactory. Ce modle de donnes reprsente une unit de production prvue pour un nouveau site Dublin en Irlande. Le code localise trois machines au sein de lusine, charge les caisses de produits sur deux dentre elles, puis cre des commandes

256

Partie IV

Patterns dopration

partir de la hirarchie Command. La dernire instruction du programme appelle la mthode execute() dun objet CommandSequence pour que le robot excute les actions contenues dans la commande seq. Un objet CommandSequence interprte lopration execute() en transmettant lappel chaque sous-commande :
package com.oozinoz.robotInterpreter; import java.util.ArrayList; import java.util.List; public class CommandSequence extends Command { protected List commands = new ArrayList(); public void add(Command c) { commands.add(c); } public void execute() { for (int i = 0; i < commands.size(); i++) { Command c = (Command) commands.get(i); c.execute(); } } }

La classe CarryCommand interprte lopration execute() en interagissant avec le robot pour dplacer une caisse dune machine vers une autre :
package com.oozinoz.robotInterpreter; import com.oozinoz.machine.Machine; public class CarryCommand extends Command { protected Machine fromMachine; protected Machine toMachine; public CarryCommand( Machine fromMachine, Machine toMachine) { this.fromMachine = fromMachine; this.toMachine = toMachine; } public void execute() { Robot.singleton.carry(fromMachine, toMachine); } }

Chapitre 25

INTERPRETER

257

La classe CarryCommand a t conue pour fonctionner spciquement dans le domaine dune ligne de production contrle par des robots. On peut facilement imaginer dautres classes spciques un domaine, telles quune classe StartUpCommand ou ShutdownCommand pour contrler les machines. Il serait galement utile davoir une classe ForCommand qui excute une commande travers un ensemble de machines. La Figure 25.2 illustre ces extensions de la hirarchie Command.
Figure 25.2
Le pattern INTERPRETER permet plusieurs sousclasses de rinterprter la signication dune opration commune.
Command

execute()

CommandSequence

CarryCommand

ForCommand

StartUpCommand

ShutdownCommand

Une partie de la conception de la classe ForCommand apparat claire demble. Le constructeur de cette classe accepterait vraisemblablement une collection de machines et un objet COMMAND qui serait excut en tant que corps dune boucle for. La partie la plus dlicate est la liaison de la boucle et du corps. Java 5 possde une instruction for tendue qui tablit une variable recevant une nouvelle valeur chaque fois que le corps est excut. Nous mulerons cette approche. Considrez linstruction suivante :
for (Command c: commands) c.execute();

Java associe lidentiant c dclar par linstruction for la variable c du corps de la boucle. Pour crer une classe INTERPRETER qui mule cela, nous avons besoin dun mcanisme pour grer et valuer des variables. La Figure 25.3 prsente une hirarchie Term qui sert cela.

258

Partie IV

Patterns dopration

com.oozinoz.robotInterpreter2

Term

eval():Machine

Variable name:String Variable(name:String) assign(t:Term) equals(:Object):boolean eval():Machine

Constant machine:Machine Constant(m:Machine) equals(:Object):boolean eval():Machine

Figure 25.3
La hirarchie Term fournit des variables pouvant reprsenter des machines.

La hirarchie Term est semblable la hirarchie Command en ce quune certaine opration, en loccurrence eval(), apparat tous les niveaux. Vous pourriez penser que cette hirarchie est elle-mme un exemple de INTERPRETER, malgr labsence de classes de composition, telles que CommandSequence, qui accompagnent gnralement ce pattern. Cette hirarchie permet de nommer des machines individuelles en tant que constantes et dassigner des variables ces constantes ou dautres variables. Elle apporte aussi plus de souplesse aux classes INTERPRETER spciques un domaine. Par exemple, le code de StartUpCommand peut tre conu pour fonctionner avec un objet Term plutt quavec une machine spcique :
package com.oozinoz.robotInterpreter2; import com.oozinoz.machine.Machine; public class StartUpCommand extends Command { protected Term term;

Chapitre 25

INTERPRETER

259

public StartUpCommand(Term term) { this.term = term; } public void execute() { Machine m = term.eval(); m.startup(); } }

De mme, pour ajouter plus de souplesse la classe CarryCommand, nous pouvons la modier pour quelle fonctionne avec des objets Term:
package com.oozinoz.robotInterpreter2; public class CarryCommand extends Command { protected Term from; protected Term to; public CarryCommand(Term fromTerm, Term toTerm) { from = fromTerm; to = toTerm; } public void execute() { Robot.singleton.carry(from.eval(), to.eval()); } }

Aprs avoir conu la hirarchie Command pour quelle fonctionne avec des objets Term, nous pouvons crire la classe ForCommand de faon quelle dnisse la valeur dune variable et excute une commande body dans une boucle :
package com.oozinoz.robotInterpreter2; import import import import java.util.List; com.oozinoz.machine.Machine; com.oozinoz.machine.MachineComponent; com.oozinoz.machine.MachineComposite; ForCommand extends Command { MachineComponent root; Variable variable; Command body;

public class protected protected protected

public ForCommand( MachineComponent mc, Variable v, Command body) { this.root = mc; this.variable = v; this.body = body; }

260

Partie IV

Patterns dopration

public void execute() { execute(root); } private void execute(MachineComponent mc) { if (mc instanceof Machine) { // Exercice ! return; } MachineComposite comp = (MachineComposite) mc; List children = comp.getComponents(); for (int i = 0; i < children.size(); i++) { MachineComponent child = (MachineComponent) children.get(i); execute(child); } } }

Le code execute() de la classe ForCommand recourt au transtypage (casting) pour parcourir un arbre de composant-machine. Le Chapitre 28, sur le pattern ITERATOR, prsente des techniques plus rapides et plus lgantes pour explorer un composite. Pour le pattern INTERPRETER, limportant est dinterprter correctement la requte execute() pour chaque nud de larbre. Exercice 25.1 Compltez le code de la mthode execute() de la classe ForCommand.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

La classe ForCommand nous permet de commencer composer des programmes, ou scripts, de commandes pour lunit de production. Voici par exemple un programme qui compose un objet interprteur qui arrte toutes les machines dans lunit :
package app.interpreter; import com.oozinoz.machine.*; import com.oozinoz.robotInterpreter2.*; class ShowDown { public static void main(String[] args) {

Chapitre 25

INTERPRETER

261

MachineComposite dublin = OozinozFactory.dublin(); Variable v = new Variable("machine"); Command c = new ForCommand( dublin, v, new ShutDownCommand(v)); c.execute(); } }

Lorsque ce programme appelle la mthode execute(), lobjet ForCommand c linterprte en traversant le composant-machine fourni et, pour chaque machine :
m m

Il dnit la valeur de la variable v. Il invoque lopration execute() de lobjet ShutDownCommand fourni.

Si nous ajoutons des classes qui contrlent le ux logique, telles quune classe IfCommand et une classe WhileCommand, nous pouvons crer un interprteur riche en fonctionnalits. Ces classes doivent disposer dun moyen pour modliser une condition boolenne. Nous pourrions par exemple avoir besoin de modliser si une variable machine est gale une certaine machine. A cet effet, nous pourrions introduire une nouvelle hirarchie dobjets Term, bien quil soit plus simple demprunter une ide du langage C : nous disons que null signie faux et que tout le reste signie vrai. Nous pouvons ainsi tendre la hirarchie Term, comme le montre la Figure 25.4.
Figure 25.4
La hirarchie Term inclut des classes qui modlisent des conditions boolennes.
Term

eval():Machine

Equals term1:Term term2:Term equals(t1:Term,t2:Term) eval():Machine

HasMaterial term:Term hasMaterial(t:Term) eval():Machine

Variable

Constant

262

Partie IV

Patterns dopration

La classe Equals compare deux termes et retourne null pour signier que la condition dgalit est fausse. Une conception raisonnable serait de faire en sorte que la mthode eval() de cette classe retourne un de ses termes si lgalit est vraie, comme suit :
package com.oozinoz.robotInterpreter2; import com.oozinoz.machine.Machine; public class Equals extends Term { protected Term term1; protected Term term2; public Equals(Term term1, Term term2) { this.term1 = term1; this.term2 = term2; } public Machine eval() { Machine m1 = term1.eval(); Machine m2 = term2.eval(); return m1.equals(m2) ? m1 : null; } }

La classe HasMaterial tend lide dune valeur de classe boolenne un exemple spcique un domaine :
package com.oozinoz.robotInterpreter2; import com.oozinoz.machine.Machine; public class HasMaterial extends Term { protected Term term; public HasMaterial(Term term) { this.term = term; } public Machine eval() { Machine m = term.eval(); return m.hasMaterial() ? m : null; } }

Maintenant que nous avons ajout lide de termes boolens notre package interprteur, nous pouvons ajouter des classes de contrle de ux, comme illustr Figure 25.5.

Chapitre 25

INTERPRETER

263

Figure 25.5
Nous pouvons obtenir un interprteur plus riche en fonctionnalits en ajoutant sa hirarchie des classes de contrle de ux.
Command

execute()

CommandSequence

CarryCommand

ForCommand

StartUpCommand

IfCommand

ShutdownCommand

WhileCommand

NullCommand

La classe NullCommand est utile pour les cas o nous avons besoin dune commande qui ne fait rien, comme lorsque la branche else dune instruction if est vide :
package com.oozinoz.robotInterpreter2; public class NullCommand extends Command { public void execute() { } } package com.oozinoz.robotInterpreter2; public class protected protected protected IfCommand extends Command { Term term; Command body; Command elseBody;

public IfCommand( Term term, Command body, Command elseBody) { this.term = term; this.body = body; this.elseBody = elseBody; }

264

Partie IV

Patterns dopration

public void execute() { // Exercice ! } }

Exercice 25.2 Compltez le code de la mthode execute() de la classe IfCommand.

Exercice 25.3 Ecrivez le code de la classe WhileCommand.

Nous pourrions utiliser la classe WhileCommand avec un interprteur qui dcharge une presse toiles :
package app.interpreter; import com.oozinoz.machine.*; import com.oozinoz.robotInterpreter2.*; public class ShowWhile { public static void main(String[] args) { MachineComposite dublin = OozinozFactory.dublin(); Term starPress = new Constant( (Machine) dublin.find("StarPress:1401")); Term fuser = new Constant( (Machine) dublin.find("Fuser:1101")); starPress.eval().load(new Bin(77)); starPress.eval().load(new Bin(88)); WhileCommand command = new WhileCommand( new HasMaterial(starPress), new CarryCommand(starPress, fuser)); command.execute(); } }

Lobjet command est un interprteur qui interprte execute() pour signier de dcharger toutes les caisses de la presse 1401.

Chapitre 25

INTERPRETER

265

Exercice 25.4 Fermez ce livre et expliquez brivement la diffrence entre COMMAND et INTERPRETER.

Nous pourrions ajouter dautres classes la hirarchie de linterprteur pour avoir plus de types de contrle ou pour des tches relevant dautres domaines. Nous pourrions aussi tendre la hirarchie Term. Par exemple, il pourrait tre utile de disposer dune sous-classe Term qui localise un tampon de dchargement proche dune autre machine. Les utilisateurs des hirarchies Command et Term peuvent composer des "programmes" dexcution complexes. Par exemple, il ne serait pas trop difcile de crer un objet qui, lorsquil sexcute, dcharge tous les produits de toutes les machines, lexception des tampons de dchargement. Voici le pseudo-code dun tel programme :
for (m dans usine) if (not (m est tamponDechargement)) td = chercheMethDecharg pour m while (m contientProduit) transporte (m, td)

Si nous crivions du code Java pour accomplir ces tches, le rsultat serait plus volumineux et moins simple que le pseudo-code. Aussi, pourquoi ne pas transformer ce dernier en du code rel en crant un analyseur syntaxique capable de lire un langage spcique un domaine pour manipuler les produits de lusine et de crer des objets interprteur notre place ?

Interprteurs, langages et analyseurs syntaxiques


Le pattern INTERPRETER spcie comment les interprteurs fonctionnent mais pas comment il faut les instancier ou les composer. Dans ce chapitre, vous avez cr des interprteurs manuellement, en crivant directement les lignes de code Java. Une approche plus courante est dutiliser un analyseur syntaxique (parser), cest-dire un objet qui peut reconnatre du texte et dcomposer sa structure partir dun ensemble de rgles pour le mettre dans une forme adapte un traitement subsquent. Vous pourriez par exemple crire un analyseur qui cre un interprteur de commandes machine correspondant au pseudo-code prsent plus haut.

266

Partie IV

Patterns dopration

Au moment de la rdaction du prsent livre, il existait peu doutils pour analyser le code Java et seulement quelques ouvrages sur le sujet. Pour dterminer si un support plus large a t dvelopp depuis, recherchez sur le Web la chane "Java parser tools". La plupart des kits doutils danalyse incluent un gnrateur de parser. Pour lutiliser, vous devez employer une syntaxe spciale dcrivant la grammaire de votre langage, et loutil gnrera un analyseur partir de votre description. Cet analyseur reconnatra ensuite les instances de votre langage. Vous pouvez sinon crire vous-mme un analyseur caractre gnral en appliquant le pattern INTERPRETER. Louvrage Building Parsers with JavaTM [Metsker 2001] explique cette technique, avec des exemples en Java.

Rsum
Le pattern INTERPRETER permet de composer des objets excutables partir dune hirarchie de classes que vous crez. Chaque classe implmente une opration commune qui possde habituellement un nom gnrique, tel que execute(). Bien que les exemples de ce chapitre ne le montrent pas, cette mthode reoit souvent un objet "contexte" additionnel qui stocke un tat important. Le nom de chaque classe rete gnralement la faon dont elle implmente, ou interprte, lopration commune. Chacune delles dnit un moyen de composer des commandes ou bien reprsente une commande terminale qui entrane une action. Les interprteurs saccompagnent souvent dune conception supportant lintroduction de variables et dexpressions boolennes ou arithmtiques. Ils collaborent souvent aussi avec un analyseur syntaxique pour crer un petit langage qui simplie la gnration de nouveaux objets interprteur.

V
Patterns dextension

26
Introduction aux extensions
Lorsque vous programmez en Java, vous ne partez pas de zro mais "hritez" de toute la puissance des bibliothques de classes de ce langage. Vous hritez aussi habituellement du code de vos prdcesseurs et de vos collgues. Lorsque vous ne rorganisez ou namliorez pas le code existant, vous ltendez. Vous pourriez dire que la programmation en Java est une extension. Il vous est peut-tre dj arriv dhriter dune base de code dont la qualit tait mdiocre. Mais le code que vous ajoutez est-il rellement meilleur ? La rponse est parfois subjective. Ce chapitre passe en revue certains principes du dveloppement orient objet qui vous permettront dvaluer la qualit de votre travail. Outre les techniques classiques dextension dune base de code, vous pourriez appliquer des patterns de conception pour ajouter de nouveaux comportements. Aprs avoir expos les principes de la conception oriente objet dans le cadre dun dveloppement ordinaire, cette section revoit certains patterns contenant un lment dextension et introduit ceux non encore abords.

Principes de la conception oriente objet Les ponts de pierre existent depuis plusieurs milliers dannes, ce qui a laiss lhomme le temps de dvelopper des principes de conception bien prouvs. La programmation oriente objet existe, elle, depuis peut-tre cinquante ans, aussi nest-il pas surprenant que ses principes de conception en soient un stade moins avanc. Nous disposons toutefois dexcellents forums sur les principes reconnus actuellement, lun des meilleurs tant Portland Pattern Repository ladresse www.c2.com [Cunningham]. Vous trouverez sur ce site les principes les plus efcaces pour valuer des conceptions OO. Lun deux, notamment, est le principe de substitution de Liskov.

270

Partie V

Patterns dextension

Le principe de substitution de Liskov


Les nouvelles classes devraient tre des extensions logiques et cohrentes de leurs super-classes. Un compilateur Java garantit un certain niveau de cohrence, mais nombre des principes de cohrence lui chappent. Une rgle qui peut vous aider amliorer vos conceptions est le principe de substitution de Liskov, ou LSP (Liskov Substitution Principle) [Liskov 1987], qui peut tre paraphras comme suit : Une instance dune classe devrait fonctionner comme une instance de sa superclasse. Une conformit basique avec ce principe est intgre aux langages OO, tels que Java. Par exemple, il est valide de se rfrer un objet UnloadBuffer (tampon de dchargement) en tant que Machine puisque UnloadBuffer est une sous-classe de Machine:
Machine m = new UnloadBuffer(3501);

Certains aspects de la conformit avec LSP font appel lintelligence humaine, ou en tout cas plus dintelligence que nen possdent les compilateurs actuels. Considrez les hirarchies de classes de la Figure 26.1.
Figure 26.1
Ce diagramme sapplique aux questions suivantes : un tampon de dchargement est-il une machine ? Un cercle est-il une ellipse ?
Machine Ellipse

addTub(t:Tub) getTubs():List

setWidth() setHeight()

UnloadBuffer

Circle

addTub(t:Tub) getTubs():List

setRadius()

Un tampon de dchargement est certainement une machine, mais modliser ce fait dans une hirarchie de classes peut donner lieu des problmes. Chez Oozinoz, toutes les machines, lexception des tampons de dchargement, peuvent recevoir un bac (tub) de produits chimiques et signaler les bacs qui se trouvent ct delles. Aussi est-il utile de remonter ce comportement au niveau de la classe Machine.

Chapitre 26

Introduction aux extensions

271

Mais cest une erreur que dinvoquer addTub() ou getTubs() sur un objet UnloadBuffer. Devrions-nous gnrer des exceptions si de tels appels avaient lieu ? Supposez quun autre dveloppeur crive une mthode qui interroge toutes les machines dune trave pour crer une liste complte de tous les bacs de produits prsents dans cette trave. Lorsque ce code arrive un tampon de dchargement, il rencontre une exception si la mthode getTubs() de la classe UnloadBuffer en gnre une. Il sagit dune violation totale du principe de Liskov : si vous utilisez un objet UnloadBuffer en tant quobjet Machine, votre programme peut planter. Au lieu de gnrer une exception, imaginez que nous dcidions dignorer simplement les appels de getTubs() et addTub() dans la classe UnloadBuffer, ce qui reprsente toujours une violation de ce principe : si vous ajoutez un bac une machine, le bac risque de disparatre. Ces violations ne constituent pas toujours des erreurs de conception. Dans le cas des machines dOozinoz, il convient dvaluer lintrt de placer dans la classe Machine des comportements qui sappliquent presque toutes les machines par rapport aux inconvnients lis au non-respect de LSP. Limportant est de connatre ce principe et dtre capable de dterminer en quoi certaines considrations de conception justient sa violation. Exercice 26.1 Un cercle est certainement un cas spcial dellipse. Dterminez nanmoins si la relation des classes Ellipse et Circle dans la Figure 26.1 est une violation de LSP.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B. La loi de Demeter
Vers la n des annes 80, des membres du Demeter Project la Northeastern University de Boston ont tent de codier les rgles garantissant la "bonne sant" dun programme, leur attribuant le nom de loi de Demeter, ou LOD (Law Of Demeter). Karl Lieberherr et Ian Holland [1989] en ont donn une description dtaille dans un article intitul "Assuring good style for object-oriented programs", qui dit ceci : De manire informelle, la loi stipule que chaque mthode peut envoyer des messages uniquement un ensemble limit dobjets : aux objets argument, la pseudo-variable [this] et aux sous-parties immdiates de [this].

272

Partie V

Patterns dextension

Larticle donne galement une dnition plus formelle, mais il est plus facile de donner des exemples de violations de cette loi que de tenter dexpliquer son propos. Supposez que vous disposiez dun objet MaterialManager avec une mthode qui reoit un objet Tub en tant que paramtre. Les objets Tub possdent une proprit Location qui retourne lobjet Machine reprsentant lemplacement du bac. Dans la mthode de MaterialManager, vous avez besoin de savoir si la machine est active et disponible. Vous pourriez crire le code suivant cet effet :
if (tub.getLocation().isUp()) { //... }

Ce code enfreint la loi de Demeter car il envoie un message tub.getLocation(). Lobjet tub.getLocation() nest pas un paramtre, nest pas this lobjet MaterialManager dont la mthode est excute et nest pas non plus un attribut de this. Exercice 26.2 Expliquez pourquoi lexpression tub.getLocation().isUp() peut tre considre comme tant de prfrence viter.

Cet exercice pourrait banaliser la valeur de cette loi sil suggrait quelle signie seulement que les expressions de la forme a.b.c sont viter. En fait, Lieberherr et Holland lui donnent un sens plus large et rpondent afrmativement la question : "Existe-t-il une formule ou une rgle permettant dcrire des programmes orients objet de bonne qualit ?" Je vous conseille de lire larticle original expliquant cette loi. A linstar du principe de Liskov, elle vous aidera dvelopper de meilleurs programmes si vous connaissez les rgles, savez les suivre, et savez aussi quand votre conception peut les enfreindre. Vous pouvez constater quen suivant un ensemble de recommandations, vos extensions produisent automatiquement du code performant. Mais, aux yeux de nombreux programmeurs, le dveloppement OO reste un art. Lextension efcace dune base de code semble tre le rsultat dun ensemble de pratiques labores par des artisans qui en sont encore formuler et codier leur art. La refactorisation dsigne lemploi dune collection doutils de modication de code conus pour amliorer la qualit dune base de code sans changer sa fonction.

Chapitre 26

Introduction aux extensions

273

Elimination des erreurs potentielles


Vous pourriez penser que le principe de substitution de Liskov et la loi de Demeter vous empcheront dcrire du code mdiocre. En fait, vous utiliserez plutt ces rgles pour identier le code de mauvaise qualit et lamliorer. Cest une pratique normale : crire du code qui tourne puis le perfectionner en identiant et en corrigeant les problmes potentiels. Mais comment identie-t-on ces problmes ? Au moyen dindicateurs (code smell). Louvrage Refactoring: Improving the Design of Existing Code [Fowler et al. 1999], en dnombre vingt-deux et dcrit les refactorisations correspondantes. Le prsent livre a eu recours de nombreuses fois la refactorisation pour rorganiser et amliorer du code existant en appliquant un pattern. Mais vous navez pas ncessairement besoin dappliquer un pattern de conception lors de la refactorisation. Chaque fois que le code dune mthode est susceptible de poser problme, il devrait tre revu. Exercice 26.3 Donnez un exemple dune mthode pouvant tre refactorise sans enfreindre pour autant le principe de Liskov ou la loi de Demeter.

Au-del des extensions ordinaires


Lobjectif de beaucoup de patterns de conception, dont nombre dentre eux ont dj t couverts, a trait de prs ou de loin lextension de comportements. Ces patterns clarient souvent le rle de deux dveloppeurs. Par exemple, dans le pattern ADAPTER, un dveloppeur peut fournir un service utile ainsi quune interface pour les objets qui veulent utiliser ce service. En plus des patterns dj couverts, trois autres patterns ont comme principal objectif dtendre du code existant.
Si vous envisagez de Permettre aux dveloppeurs de composer dynamiquement le comportement dun objet Appliquez le pattern

DECORATOR

274

Partie V

Patterns dextension

Si vous envisagez de Offrir un moyen daccder aux lments dune collection de faon squentielle Permettre aux dveloppeurs de dnir une nouvelle opration pour une hirarchie sans changer les classes qui la composent

Appliquez le pattern

ITERATOR VISITOR

Exercice 26.4 Compltez le tableau suivant, qui donne des exemples dutilisation des patterns dj abords pour tendre le comportement dune classe ou dun objet.
Exemple Le concepteur dune simulation pyrotechnique tablit une interface qui dnit le comportement que doit prsenter votre objet pour pouvoir prendre part la simulation. Un kit doutils permet de composer des objets excutables lors de lexcution. ? ? Un gnrateur de code insre un comportement qui donne lillusion quun objet sexcutant sur une autre machine est local. ? Une conception permet de dnir des oprations abstraites qui dpendent dune interface spcique et dajouter de nouveaux drivers qui satisfont aux exigences de cette interface. Pattern luvre

ADAPTER

TEMPLATE METHOD COMMAND


?

OBSERVER
?

Rsum
Ecrire du code revient souvent tendre celui existant pour apporter de nouvelles fonctionnalits, puis le rorganiser an damliorer sa qualit. Il nexiste pas de technique infaillible pour valuer la qualit dun programme, mais certains principes de bonne conception OO ont nanmoins t dnis.

Chapitre 26

Introduction aux extensions

275

Le principe de substitution de Liskov suggre quune instance dune classe devrait fonctionner comme une instance de sa super-classe. Vous devriez connatre et tre capable de justier les violations de ce principe dans votre code. La loi de Demeter est un ensemble de rgles qui aident rduire les dpendances entre les classes et clarier le code. Martin Fowler et al. [1999] ont labor une srie dindicateurs permettant didentier le code de qualit mdiocre. Chaque indicateur donne lieu une ou plusieurs refactorisations, certaines dentre elles visant lapplication dun pattern de conception. Nombre de patterns servent de techniques pour clarier, simplier ou faciliter les extensions.

27
DECORATOR
Pour tendre une base de code, vous lui ajoutez normalement de nouvelles classes ou mthodes. Parfois, vous avez besoin de composer un objet avec un nouveau comportement lors de lexcution. Le pattern INTERPRETER, par exemple, permet de gnrer un objet excutable dont le comportement change radicalement selon la faon dont vous le composez. Dans certains cas, vous aurez besoin de pouvoir combiner plusieurs variations comportementales moins importantes et utiliserez pour cela le pattern DECORATOR. Lobjectif du pattern DECORATOR est de vous permettre de composer de nouvelles variations dune opration lors de lexcution.

Un exemple classique : ux dE/S et objets Writer


La conception densemble des ux dentre et de sortie dans les bibliothques de classes Java constitue un exemple classique du pattern DECORATOR. Un ux (stream) est une srie doctets ou de caractres, tels ceux contenus dans un document. Dans Java, les classes dcriture, ou Writer, reprsentent une faon de supporter les ux. Certaines de ces classes possdent un constructeur qui accepte un objet Writer, ce qui signie que vous pouvez crer un Writer partir dun Writer. Ce type de composition simple est la structure typique du pattern DECORATOR, qui est prsent dans les classes dcriture Java. Mais, comme nous le verrons, il ne faut pas beaucoup de code DECORATOR pour nous permettre dtendre grandement notre capacit combiner des variations doprations de lecture et dcriture.

278

Partie V

Patterns dextension

Pour un exemple de DECORATOR dans Java, considrez le code suivant qui cre un petit chier texte :
package app.decorator; import java.io.*; public class ShowDecorator { public static void main(String[] args) throws IOException { FileWriter file = new FileWriter("sample.txt"); BufferedWriter writer = new BufferedWriter(file); writer.write("un petit exemple de texte"); writer.newLine(); writer.close(); } }

Lexcution de ce programme produit un chier sample.txt qui contient une petite quantit de texte. Le code utilise un objet FileWriter pour crer un chier, en enveloppant cet objet dans un objet BufferedWriter. Ce quil importe de retenir ici, cest que nous composons un ux, BufferedWriter, partir dun autre ux, FileWriter. Chez Oozinoz, le personnel de vente a besoin de mettre en forme des messages personnaliss partir du texte stock dans la base de donnes de produits. Ces messages nutilisent pas des polices ou des styles trs varis. Nous crerons pour cela un framework de dcorateurs. Ces classes nous permettront de composer une grande varit de ltres de sortie.
Figure 27.1
La classe OozinozFilter est parente des classes qui mettent en forme les ux de caractres en sortie.
Writer

FilterWriter

FilterWriter(:Writer) close() write(:char[],offset:int,length:int) write(ch:int) write(:String,offset:int,length:int) ...

OozinozFilter

Chapitre 27

DECORATOR

279

Pour dvelopper une collection de classes de ltrage, il est utile de crer une classe abstraite qui dnit les oprations que ces ltres doivent supporter. En slectionnant des oprations qui existent dj dans la classe Writer, vous pouvons crer presque sans effort une autre classe qui hrite tous ses comportements de cette clas se. La Figure 27.1 illustre cette conception. Notre super-classe de ltrage doit possder plusieurs attributs essentiels pour pouvoir supporter des ux de sortie composables :
m m m

Elle doit accepter dans son constructeur un objet Writer. Elle doit agir en tant que super-classe dune hirarchie de ltres. Elle doit fournir des implmentations par dfaut de toutes les mthodes dcriture sauf write(:int).

La Figure 27.2 illustre cette conception.


Figure 27.2
Le constructeur de la classe OozinozFilter accepte une instance de nimporte quelle sous-classe de Writer.
Writer

FilterWriter

OozinozFilter

OozinozFilter(:Writer) write(:char[],offset:int,length:int) write(c:int) write(:String,offset:int,length:int) ...

LowerCaseFilter

UpperCaseFilter

RandomCaseFilter

WrapFilter

CommaListFilter

280

Partie V

Patterns dextension

La classe OozinozFilter rpond aux exigences de conception en peu de lignes :


package com.oozinoz.filter; import java.io.*; public abstract class OozinozFilter extends FilterWriter { protected OozinozFilter(Writer out) { super(out); } public void write(char cbuf[], int offset, int length) throws IOException { for (int i = 0; i < length; i++) write(cbuf[offset + i]); } public abstract void write(int c) throws IOException; public void write(String s, int offset, int length) throws IOException { write(s.toCharArray(), offset, length); } }

Ce code est tout ce dont nous avons besoin pour faire intervenir DECORATOR. Les sous-classes de OozinozFilter peuvent fournir de nouvelles implmentations de write(:int) qui modient un caractre avant de le passer la mthode write(:int) du ux sous-jacent. Les autres mthodes de OozinozFilter fournissent le comportement typiquement requis par les sous-classes. Cette classe laisse simplement les appels de close() et flush() sa classe parent, FilterWriter. Elle interprte galement write(:char[]) par rapport la mthode write(:int) quelle laisse abstraite. A prsent, il est ais de crer et dutiliser de nouveaux ltres de ux. Par exemple, le code suivant convertit le texte en minuscules :
package com.oozinoz.filter; import java.io.*; public class LowerCaseFilter extends OozinozFilter { public LowerCaseFilter(Writer out) { super(out); } public void write(int c) throws IOException { out.write(Character.toLowerCase((char) c)); } }

Chapitre 27

DECORATOR

281

Voici un exemple de programme qui utilise un ltre de conversion en minuscules :


package app.decorator; import java.io.IOException; import java.io.Writer; import com.oozinoz.filter.ConsoleWriter; import com.oozinoz.filter.LowerCaseFilter; public class ShowLowerCase { public static void main(String[] args) throws IOException { Writer out = new ConsoleWriter(); out = new LowerCaseFilter(out); out.write("Ce Texte doit tre crit TOUT en MiNusculeS !"); out.close(); } }

Ce programme afche "ce texte doit tre crit tout en minuscules !" sur la console. Le code de la classe UpperCaseFilter est identique celui de LowerCaseFilter, lexception de la mthode write(), que voici :
public void write(int c) throws IOException { out.write(Character.toUpperCase((char) c)); }

Le code de la classe TitleCaseFilter est un peu plus complexe puisquil doit garder trace des espaces :
package com.oozinoz.filter; import java.io.*; public class TitleCaseFilter extends OozinozFilter { boolean inWhite = true; public TitleCaseFilter(Writer out) { super(out); } public void write(int c) throws IOException { out.write( inWhite ? Character.toUpperCase((char) c) : Character.toLowerCase((char) c)); inWhite = Character.isWhitespace((char) c) || c == "; } }

282

Partie V

Patterns dextension

La classe CommaListFilter insre une virgule entre des lments :


package com.oozinoz.filter; import java.io.IOException; import java.io.Writer; public class CommaListFilter extends OozinozFilter { protected boolean needComma = false; public CommaListFilter(Writer writer) { super(writer); } public void write(int c) throws IOException { if (needComma) { out.write(,); out.write( ); } out.write(c); needComma = true; } public void write(String s) throws IOException { if (needComma) out.write(", "); out.write(s); needComma = true; } }

Le rle de ces ltres est le mme : la tche de dveloppement consiste remplacer les mthodes write() appropries. Ces mthodes mettent en forme le ux de texte reu puis le passent un ux subordonn. Exercice 27.1 Ecrivez le code de RandomCaseFilter.java.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

Le code de la classe WrapFilter est beaucoup plus complexe que les autres ltres. Il aligne son rsultat au centre et doit donc mettre en tampon et compter les caractres avant de les passer son ux subordonn. Vous pouvez examiner ce code en le

Chapitre 27

DECORATOR

283

tlchargeant partir de www.oozinoz.com (voir lAnnexe C pour les instructions de tlchargement du code). Le constructeur de WrapFilter accepte un objet Writer ainsi quun paramtre de largeur lui indiquant quand aller la ligne. Vous pouvez combiner ce ltre et dautres ltres pour crer une varit deffets. Par exemple, le programme suivant aligne au centre le texte dun chier en entre, en insrant des retours la ligne et en mettant en capitale la premire lettre de chaque mot :
package app.decorator; import java.io.*; import com.oozinoz.filter.TitleCaseFilter; import com.oozinoz.filter.WrapFilter; public class ShowFilters { public static void main(String args[]) throws IOException { BufferedReader in = new BufferedReader( new FileReader(args[0])); Writer out = new FileWriter(args[1]); out = new WrapFilter(new BufferedWriter(out), 40); out = new TitleCaseFilter(out); String line; while ((line = in.readLine()) != null) out.write(line + "\n"); out.close(); in.close(); } }

Pour voir le rsultat de ce programme, supposez quun chier adcopy.txt contienne le texte suivant :
The "SPACESHOT" shell hovers at 100 meters for 2 to 3 minutes, erupting star bursts every 10 seconds that generate ABUNDANT reading-level light for a typical stadium.

Vous pourriez excuter le programme ShowFilters partir de la ligne de commande, comme ceci :
>ShowFilters adcopy.txt adout.txt

Le contenu du chier adout.txt apparatrait ainsi :


The "Spaceshot" Shell Hovers At 100 Meters For 2 To 3 Minutes, Erupting Star

284

Partie V

Patterns dextension

Bursts Every 10 Seconds That Generate Abundant Reading-level Light For A Typical Stadium.

Plutt que dcrire dans un chier, il peut tre utile denvoyer les caractres vers la console. La Figure 27.3 illustre la conception dune classe qui tend Writer et dirige la sortie vers la console.
Figure 27.3
Un objet ConsoleWriter peut servir dargument au constructeur de nimporte laquelle des sous-classes de OozinozFilter.
Writer

FilterWriter

OozinozFilter

OozinozFilter(:Writer) write(:char[],offset:int,length:int) write(c:int) write(:String,offset:int,length:int) ...

LowerCaseFilter

UpperCaseFilter

RandomCaseFilter

WrapFilter

CommaListFilter

Exercice 27.2 Ecrivez le code de ConsoleWriter.java.

Chapitre 27

DECORATOR

285

Les ux dentre et de sortie reprsentent un exemple classique de la faon dont le pattern DECORATOR permet dassembler le comportement dun objet au moment de lexcution. Une autre application importante de ce pattern intervient lorsque vous avez besoin de crer des fonctions mathmatiques lexcution.

Enveloppeurs de fonctions
Le principe de composer de nouveaux comportements lors de lexcution au moyen du pattern DECORATOR sapplique aussi bien aux ux dentre/sortie quaux fonctions mathmatiques. La possibilit de crer des fonctions lors de lexcution est quelque chose dont vous pouvez faire proter les utilisateurs, en leur permettant de spcier de nouvelles fonctions via une interface GUI ou un petit langage. Vous pouvez aussi simplement vouloir rduire le nombre de mthodes prsentes dans votre code et offrir davantage de souplesse en crant des fonctions mathmatiques en tant quobjets. Pour crer une bibliothque de dcorateurs de fonctions, ou "enveloppeurs" de fonctions, nous pouvons appliquer une structure semblable celle utilise pour les ux dentre/sortie. Nous nommerons Function la super-classe enveloppeur. Pour la conception initiale de cette classe, nous pourrions copier la conception de la classe OozinozFilter, comme illustr Figure 27.4. La classe OozinozFilter tend FilterWriter et son constructeur sattend recevoir un objet Writer. La conception de la classe Function est analogue, sauf quau lieu de recevoir un seul objet IFunction, elle accepte un tableau. Certaines fonctions, telles celles arithmtiques, ncessitent plus dune fonction subordonne. Dans le cas des enveloppeurs de fonctions, aucune classe existante telle que Writer nimplmente lopration dont nous avons besoin. Nous pouvons donc nous passer dune interface IFunction et dnir plus simplement la hirarchie Function sans cette interface, comme le montre la Figure 27.5. A linstar de la classe OozinozFilter, la classe Function dnit une opration commune que ses sous-classes doivent implmenter. Un choix naturel pour le nom de cette opration est f. Nous pourrions prvoir dimplmenter des fonctions paramtriques fondes sur un paramtre de temps qui varie de 0 1 (reportez-vous lencadr sur les quations paramtriques du Chapitre 4 pour une prsentation du sujet).

286

Partie V

Patterns dextension

Figure 27.4
La conception initiale de la hirarchie denveloppeurs de fonctions ressemble beaucoup la conception des ux dentre/sortie.
Writer

FilterWriter

OozinozFilter

OozinozFilter(:Writer) write(:char[],offset:int,length:int) write(c:int) write(:String,offset:int,length:int) ...

LowerCaseFilter

UpperCaseFilter

RandomCaseFilter

WrapFilter

CommaListFilter

Figure 27.5
Une conception simplie pour la classe Function fonctionne sans dnir dinterface spare.
Function

Function( sources[]:Function) f(:double):double

...

Chapitre 27

DECORATOR

287

Nous allons crer une sous-classe de Function pour chaque enveloppeur. La Figure 27.6 prsente une hirarchie Function initiale.
Figure 27.6
Chaque sous-classe de Function implmente la fonction f(t) de sorte quelle corresponde au nom de la classe.
Function #sources[]:Function Function(source:Function) Function(sources[]:Function) f(t:double):double

Abs

Cos

Abs(source:Function) f(t:double):double

Cos(source:Function) f(t:double):double

Arithmetic

Sin

Arithmetic( op:char, f1:Function, f2:Function) f(t:double):double

Sin(source:Function) f(t:double):double

T() Constant f(t:double):double

Constant(constant:double) f(t:double):double

288

Partie V

Patterns dextension

Le code de la super-classe Function sert principalement dclarer le tableau de sources :


package com.oozinoz.function; public abstract class Function { protected Function[] sources; public Function(Function f) { this(new Function[] { f }); } public Function(Function[] sources) { this.sources = sources; } public abstract double f(double t); public String toString() { String name = this.getClass().toString(); StringBuffer buf = new StringBuffer(name); if (sources.length > 0) { buf.append((); for (int i = 0; i < sources.length; i++) { if (i > 0) buf.append(", "); buf.append(sources[i]); } buf.append()); } return buf.toString(); } }

Les sous-classes de Function sont gnralement simples. Voici par exemple le code de la classe Cos:
package com.oozinoz.function; public class Cos extends Function { public Cos(Function f) { super(f); } public double f(double t) { return Math.cos(sources[0].f(t)); } }

Chapitre 27

DECORATOR

289

Le constructeur de Cos attend un argument Function et le passe ensuite au constructeur de la super-classe, o il est stock dans le tableau de sources. La mthode Cos.f() value la fonction source lheure t, passe cette valeur la mthode Math.Cos() et retourne le rsultat. Les classes Abs et Sin sont presque identiques Cos. La classe Constant vous permet de crer un objet Function contenant une valeur constante retourner en rponse aux appels de la mthode f(). La classe Arithmetic accepte un indicateur doprateur quelle applique sa mthode f(). Voici le code de cette classe :
package com.oozinoz.function; public class Arithmetic extends Function { protected char op; public Arithmetic(char op, Function f1, Function f2) { super(new Function[] { f1, f2 }); this.op = op; } public double f(double t) { switch (op) { case +: return sources[0].f(t) case -: return sources[0].f(t) case *: return sources[0].f(t) case /: return sources[0].f(t) default: return 0; } } }

+ sources[1].f(t); - sources[1].f(t); * sources[1].f(t); / sources[1].f(t);

La classe T retourne les valeurs de t qui ont t passes. Ce comportement est utile si vous avez besoin dune variable qui varie dans le temps de manire linaire. Par exemple, lexpression suivante cre un objet Function dont la valeur de f() varie de 0 2 mesure que le temps varie de 0 1 :
new Arithmetic(*, new T(), new Constant(2 * Math.PI))

Vous pouvez utiliser les classes Function pour composer de nouvelles fonctions mathmatiques sans avoir crire de nouvelles mthodes. La classe FunPanel accepte des arguments Function pour ses fonctions x et y. Elle adapte aussi ces

290

Partie V

Patterns dextension

fonctions la taille du canevas. Cette classe peut tre utilise par un programme comme le suivant :
package app.decorator; import app.decorator.brightness.FunPanel; import com.oozinoz.function.*; import com.oozinoz.ui.SwingFacade; public class ShowFun { public static void main(String[] args) { Function theta = new Arithmetic( *, new T(), new Constant(2 * Math.PI)); Function theta2 = new Arithmetic( *, new T(), new Constant(2 * Math.PI * 5)); Function x = new Arithmetic( +, new Cos(theta), new Cos(theta2)); Function y = new Arithmetic( +, new Sin(theta), new Sin(theta2)); FunPanel panel = new FunPanel(1000); panel.setPreferredSize( new java.awt.Dimension(200, 200)); panel.setXY(x, y); SwingFacade.launch(panel, "Chrysanthemum"); } }

Ce programme utilise une fonction qui laisse un cercle sentrelacer avec un autre plusieurs fois. Il produit le rsultat de la Figure 27.7.
Figure 27.7
Une fonction mathmatique complexe cre sans introduire aucune mthode nouvelle.

Chapitre 27

DECORATOR

291

Pour tendre votre kit denveloppeurs de fonctions, il suft dajouter de nouvelles fonctions mathmatiques la hirarchie Function. Exercice 27.3 Fermez ce livre et crivez le code de la classe enveloppeur Exp.

Supposez que la luminosit dune toile soit une onde sinusodale qui dcrot exponentiellement :
luminosit = e
4t

sin ( t )

Comme prcdemment, nous pouvons composer une fonction sans avoir crire de nouvelles classes ou mthodes :
package app.decorator.brightness; import com.oozinoz.function.*; import com.oozinoz.ui.SwingFacade; public class ShowBrightness { public static void main(String args[]) { FunPanel panel = new FunPanel(); panel.setPreferredSize( new java.awt.Dimension(200, 200)); Function brightness = new Arithmetic( *, new Exp( new Arithmetic( *, new Constant(-4), new T())), new Sin( new Arithmetic( *, new Constant(Math.PI), new T()))); panel.setXY(new T(), brightness); SwingFacade.launch(panel, "Brightness"); } }

292

Partie V

Patterns dextension

Ce code produit la courbe de la Figure 27.8.


Figure 27.8
La luminosit dune toile connat un pic soudain avant de dcrotre.

Exercice 27.4 Ecrivez le code pour dnir un objet Brightness reprsentant la fonction de luminosit.

A mesure que vous en avez besoin, vous pouvez ajouter dautres fonctions la hirarchie Function. Par exemple, des classes pour la racine carre et la tangente pourraient tre utiles. Vous pouvez aussi crer de nouvelles hirarchies applicables diffrents types, tels que des chanes, ou impliquant une dnition diffrente de lopration f(). Par exemple, f() pourrait tre dnie en tant quune fonction de temps deux ou trois dimensions. Indpendamment de la hirarchie que vous crez, vous pouvez utiliser le pattern DECORATOR pour dvelopper un riche ensemble de fonctions composables lors de lexcution.

DECORATOR en relation avec dautres patterns


Le pattern DECORATOR sappuie sur une opration commune implmente travers une hirarchie. A cet gard, il ressemble STATE, STRATEGY et INTERPRETER. Dans DECORATOR, les classes possdent aussi habituellement un constructeur qui requiert un autre objet dcorateur subordonn. Ce pattern ressemble sur ce point COMPOSITE. DECORATOR ressemble galement PROXY en ce que les classes dcorateur implmentent typiquement lopration commune en transmettant lappel lobjet dcorateur subordonn.

Chapitre 27

DECORATOR

293

Rsum
Le pattern DECORATOR permet dassembler des variations dune mme opration. Un exemple classique apparat dans les ux dentre/sortie, o vous pouvez composer un ux partir dun autre ux. Les bibliothques de classes Java supportent DECORATOR dans limplmentation des ux dentre/sortie. Vous pouvez tendre ce principe en crant votre propre ensemble de ltres dentre/sortie. Vous pouvez galement appliquer DECORATOR pour dnir des enveloppeurs de fonctions permettant de crer un large ensemble dobjets fonction partir dun jeu xe de classes de fonctions. Ce pattern donne lieu des conceptions exibles dans les situations o vous voulez pouvoir combiner les variations dimplmentation dune opration commune en de nouvelles variations au moment de lexcution.

28
ITERATOR
Etendre les fonctionnalits dun code existant en ajoutant un nouveau type de collection peut requrir lajout dun itrateur. Ce chapitre tudie le cas particulier de litration parcourant un objet composite. Outre litration dans de nouveaux types de collections, le cas dun environnement multithread soulve un certain nombre de problmes intressants qui mritent dtre analyss. Simple de prime abord, litration est en fait un problme non totalement rsolu. Lobjectif du pattern ITERATOR est de fournir un moyen daccder de faon squentielle aux lments dune collection.

Itration ordinaire
Java dispose de diffrentes approches pour raliser une itration :
m m m

les boucles for, while et repeat, se fondant gnralement sur un indice ; la classe Enumeration (dans java.util) ; la classe Iterator (galement dans java.util), ajoute dans le JDK 1.2 pour grer les collections ; les boucles for tendues (foreach), ajoutes dans le JDK 1.5.

Nous utiliserons la classe Iterator pour une grande partie de ce chapitre, cette section prsentant une boucle for tendue. Une classe Iterator possde trois mthodes : hasnext(), next() et remove(). Un itrateur a le droit de gnrer une exception du type UnsupportedOperationException sil ne supporte pas lopration remove().

296

Partie V

Patterns dextension

Une boucle for tendue suit la syntaxe suivante :


for (Type element : collection)

Cette instruction cre une boucle lisant une collection, extrayant un lment la fois (appel ici element). Il nest pas ncessaire de convertir element en un type particulier, cela tant gr de faon implicite. Cette construction peut aussi fonctionner avec des tableaux (array). Une classe qui souhaite autoriser des boucles for tendues doit implmenter une interface Iterable et inclure une mthode iterator(). Voici un programme illustrant la classe Iterator et des boucles for tendues :
package app.iterator; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class ShowForeach { public static void main(String[] args) { ShowForeach example = new ShowForeach(); example.showIterator(); System.out.println(); example.showForeach(); } public void showIterator() { List names = new ArrayList(); names.add("Fuser:1101"); names.add("StarPress:991"); names.add("Robot:1"); System.out.println("Itrateur style JDK 1.2 :"); for (Iterator it = names.iterator(); it.hasNext();) { String name = (String) it.next(); System.out.println(name); } } public void showForeach() { List<String> names = new ArrayList<String>(); names.add("Fuser:1101"); names.add("StarPress:991"); names.add("Robot:1"); System.out.println( "Boucle FOR tendue style JDK 1.5 :"); for (String name: names)

Chapitre 28

ITERATOR

297

System.out.println(name); } }

Lorsque nous excutons ce programme, nous voyons les rsultats :


Itrateur style JDK 1.2 : Fuser:1101 StarPress:991 Robot:1 Boucle FOR tendue style JDK 1.5 : Fuser:1101 StarPress:991 Robot:1

Pour linstant, la socit Oozinoz doit continuer utiliser lancien style de classes Iterator. Elle ne peut augmenter la version du code avant dtre absolument sre que ses clients disposent des nouveaux compilateurs. Vous pouvez nanmoins essayer aujourdhui les boucles for gnriques et tendues.

Itration avec scurit inter-threads


Les applications riches en fonctionnalits emploient souvent des threads pour raliser des tches sexcutant avec une apparence de simultanit. En particulier, il est frquent dexcuter en arrire-plan les tches coteuses en temps pour ne pas ralentir la ractivit de la GUI. Lutilisation de threads est utile, mais elle comporte ses risques. De nombreuses applications plantent en raison de tches sexcutant dans des threads qui ne collaborent pas efcacement. Des mthodes parcourant une collection peuvent par exemple tre en cause. Les classes de collection dans java.util.Collections offrent une certaine mesure de scurit de thread en fournissant une mthode synchronized(). En essence, elle retourne une version de la collection sous-jacente, vitant ainsi que deux threads la modient en mme temps. Une collection et son itrateur cooprent pour dtecter si une liste change durant litration, cest--dire si la liste est synchronise. Pour observer ce comportement en action, supposez que le singleton Factory dOozinoz puisse nous indiquer les machines qui sont actives un certain moment et que nous souhaitions en afcher la liste. Lexemple de code dans le package app.iterator.concurrent implmente cette liste dans la mthode upMachineNames().

298

Partie V

Patterns dextension

Le programme suivant afche une liste des machines qui sont actuellement actives, mais simule la condition que de nouvelles machines puissent entrer en action alors que le programme est en train dafcher la liste :
package app.iterator.concurrent; import java.util.*; public class ShowConcurrentIterator implements Runnable { private List list; protected static List upMachineNames() { return new ArrayList(Arrays.asList(new String[] { "Mixer1201", "ShellAssembler1301", "StarPress1401", "UnloadBuffer1501" })); } public static void main(String[] args) { new ShowConcurrentIterator().go(); } protected void go() { list = Collections.synchronizedList( upMachineNames()); Iterator iter = list.iterator(); int i = 0; while (iter.hasNext()) { i++; if (i == 2) { // simule lactivation dune machine new Thread(this).start(); try { Thread.sleep(100); } catch (InterruptedException ignored) {} } System.out.println(iter.next()); } } /** ** Insre un lment dans la liste, dans un thread distinct. */ public void run() { list.add(0, "Fuser1101"); } }

La mthode main() dans ce code construit une instance de la classe et appelle la mthode go(). Cette mthode parcourt par itration la liste des machines actives, en prenant soin den construire une version synchronise. Ce code simule la situation o une nouvelle machine devient active alors que la mthode lit la liste. La mthode run() modie la liste, sexcutant dans un thread spar.

Chapitre 28

ITERATOR

299

Le programme ShowConcurrentIterator afche une ou deux machines puis plante :


Mixer1201 java.util.ConcurrentModificationException at java.util.AbstractList$Itr.checkForComodification(Unknown Source) at java.util.AbstractList$Itr.next(Unknown Source) at com.oozinoz.app.ShowConcurrent.ShowConcurrentIterator.go(ShowConcurrent Iterator.java:49) at com.oozinoz.app.ShowConcurrent.ShowConcurrentIterator.main(ShowConcurre ntIterator.java:29) Exception in thread "main" .

Le programme plante car la liste et les objets itrateurs dtectent que la liste a chang durant litration. Vous navez pas besoin de crer un nouveau thread pour illustrer ce comportement. Vous pouvez crire un programme qui plante simplement en modiant une collection partir dune boucle dnumration. Dans la pratique, cest plus vraisemblablement par accident quune application multithread modie une liste pendant quun itrateur la parcourt. Nous pouvons laborer une approche par thread scurise pour lire une liste. Toutefois, il est important de noter que le programme ShowConcurrentIterator plante seulement parce quil utilise un itrateur. Lnumration par boucle for des lments dune liste, mme synchronise, ne dclenchera pas lexception dmontre dans le programme prcdent, mais elle pourra nanmoins rencontrer des problmes. Considrez cette version du programme :
package app.iterator.concurrent; import java.util.*; public class ShowConcurrentFor implements Runnable { private List list; protected static List upMachineNames() { return new ArrayList(Arrays.asList(new String[] { "Mixer1201", "ShellAssembler1301", "StarPress1401", "UnloadBuffer1501" })); } public static void main(String[] args) { new ShowConcurrentFor().go(); } protected void go() { System.out.println( "Cette version permet lajout concurrent" + "de nouveaux lments :");

300

Partie V

Patterns dextension

list = Collections.synchronizedList( upMachineNames()); display(); } private void display() { for (int i = 0; i < list.size(); i++) { if (i == 1) { // simule lactivation dune machine new Thread(this).start(); try { Thread.sleep(100); } catch (InterruptedException ignored) {} } System.out.println(list.get(i)); } } /** ** Insre un lment dans la liste, dans un thread distinct. */ public void run() { list.add(0, "Fuser1101"); } }

Lexcution de ce programme afche :


Cette version permet lajout concurrent de nouveaux lments : Mixer1201 Mixer1201 ShellAssembler1301 StarPress1401 UnloadBuffer1501

Exercice 28.1 Expliquez le rsultat en sortie du programme ShowConcurrentFor.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.


Nous avons examin deux versions de lexemple de programme : une qui plante et une qui produit une sortie incorrecte. Aucun de ces rsultats ntant acceptable, nous devons recourir une autre approche pour protger une liste pendant lnumration de ses lments. Il y a deux approches courantes pour coder une itration sur une collection dans une application multithread. Elles impliquent toutes deux lemploi dun objet appel

Chapitre 28

ITERATOR

301

mutex, qui est partag par des threads rivalisant pour obtenir le contrle du verrou sur lobjet. La premire possibilit de conception est de forcer tous les threads obtenir le contrle du verrou du mutex avant de pouvoir accder la collection. Voici un exemple de cette approche :
package app.iterator.concurrent; import java.util.*; public class ShowConcurrentMutex implements Runnable { private List list; protected static List upMachineNames() { return new ArrayList(Arrays.asList(new String[] { "Mixer1201", "ShellAssembler1301", "StarPress1401", "UnloadBuffer1501" })); } public static void main(String[] args) { new ShowConcurrentMutex().go(); } protected void go() { System.out.println( "Cette version synchronise correctement :"); list = Collections.synchronizedList(upMachineNames()); synchronized (list) { display(); } } private void display() { for (int i = 0; i < list.size(); i++) { if (i == 1) { // simule lactivation dune machine new Thread(this).start(); try { Thread.sleep(100); } catch (InterruptedException ignored) {} } System.out.println(list.get(i)); } } /** ** Insre un lment dans la liste, dans un thread distinct. */ public void run() { synchronized (list) { list.add(0, "Fuser1101"); } } }

302

Partie V

Patterns dextension

Ce programme afche la liste originale :


Cette version synchronise correctement : Mixer1201 ShellAssembler1301 StarPress1401 UnloadBuffer1501

La sortie du programme montre la liste telle quelle existait avant linsertion dun nouvel objet par la mthode run(). Le programme retourne un rsultat cohrent, sans duplication, car la logique du programme requiert que la mthode run() attende la n de lnumration des lments dans la mthode display(). Bien que les rsultats soient corrects, la conception peut se rvler impossible implmenter. En effet, il est probable que vous nayez pas le luxe davoir des threads bloqus pendant quun thread excute son itration. La deuxime solution consiste cloner la collection dans une opration avec un mutex puis de lire le contenu du clone. Lavantage du clonage dune liste avant de la parcourir est la vitesse. Cest une opration souvent plus rapide que dattendre quune mthode nisse son travail sur le contenu dune collection. Cette approche peut toutefois donner lieu des problmes. La mthode clone() pour ArrayList produit une copie "partielle", cest--dire une nouvelle collection qui se rfre aux mmes objets que la collection originale. Cette approche chouerait donc si dautres threads pouvaient modier les objets sous-jacents dune manire qui interfre avec votre mthode. Dans certains cas toutefois, ce risque est faible. Par exemple, si vous souhaitez simplement afcher une liste de noms de machines, il est peu probable que des noms changent au moment o votre mthode lit le clone de la liste. Pour rsumer, nous avons vu quatre approches diffrentes permettant dnumrer par itration les lments dune liste dans un environnement multithread. Deux dentre elles emploient la mthode synchronized() et sont fautives, pouvant soit planter soit produire des rsultats incorrects. Les deux dernires que nous avons tudies emploient le verrouillage et le clonage pour produire des rsultats corrects, mais elles ont aussi leurs inconvnients. Java et ses bibliothques fournissent un support substantiel pour litration dans un environnement multithread, mais ce support ne vous affranchit pas des difcults de la conception avec des processus concurrents. Les bibliothques Java offrent de

Chapitre 28

ITERATOR

303

bonnes fonctionnalits pour grer la lecture des nombreuses collections quelles proposent, mais si vous introduisez votre propre type de collection, il vous faudra aussi introduire un itrateur qui lui soit associ.

Exercice 28.2 Donnez un argument contre lemploi de la mthode synchronized(), ou justiez le fait quune approche par verrouillage nest pas non plus toujours la rponse.

Itration sur un objet composite


Il est gnralement facile de concevoir des algorithmes qui parcourent une structure composite, visitant chaque nud et ralisant certaines tches. LExercice 5.3 du Chapitre 5 vous avait demand de concevoir plusieurs algorithmes sexcutant de manire rcursive pour parcourir une structure composite. La cration dun itrateur peut tre beaucoup plus complexe que la conception dun algorithme rcursif. La difcult rside dans le transfert en retour du contrle une autre partie du programme et la conservation dun genre de signet permettant litrateur de reprendre l o il avait suspendu son travail. Les composites fournissent un bon exemple ditrateur difcile dvelopper. Vous pouvez penser que vous aurez besoin dune nouvelle classe ditrateur pour chaque composite spcique que vous crerez. Vous pouvez en fait concevoir un itrateur composite relativement rutilisable, hors le fait quil vous faudra modier vos classes de composite pour retourner le bon type ditrateur. La conception dun itrateur composite est aussi naturellement rcursive que les composites le sont eux-mmes. Pour lire par itration un composite, il faut lire ses enfants, bien que ce soit un peu plus complexe que cela puisse paratre de prime abord. Nous avons le choix entre retourner un nud avant ou aprs ses descendants (techniques appeles respectivement traverse pr-ordonne ou post-ordonne). Si nous choisissons une traverse pr-ordonne, nous devons numrer les enfants aprs avoir retourn la tte, en prenant soin de noter que chaque enfant peut luimme tre un composite. Une subtilit demande ici que nous grions deux itrateurs. Un itrateur, nomm 1 dans la Figure 28.1, garde trace de son enfant actuel.

304

Partie V

Patterns dextension

Cest un simple itrateur de liste qui parcourt la liste denfants. Un second itrateur (nomm 2) parcourt lenfant actuel en tant que composite. La Figure 28.1 illustre les trois aspects de la dtermination du nud actuel dans une itration sur un composite.
interface Iterator head:Object visited:Set ComponentEnumerator( head:Object,visited:Set) remove() hasNext():boolean next():Object remove()

ComponentIterator

CompositeIterator peek:Object children:Iterator subIterator: ComponentIterator ComponentEnumerator( head:Object, components:List, visited:Set) hasNext():bool next():Object

LeafIterator

LeafEnumerator( head:Object,visited:Set) hasNext():bool next():Object

Figure 28.1
La lecture par itration dun composite ncessite : de signaler la tte(0) ; de parcourir squentiellement les enfants (1), et de parcourir un enfant composite.

Pour notre travail de conception dun itrateur composite, nous pouvons supposer quune itration sur un nud simple sera une chose banale alors quune itration sur un nud composite sera plus difcile. En premire approximation, nous pouvons imaginer une conception ditrateurs ressemblant lexemple illustr Figure 28.2.

Chapitre 28

ITERATOR

305

ComponentIterator head:Object visited:Set ComponentEnumerator( head:Object,visited:Set) remove()

interface Iterator

hasNext():boolean next():Object remove()

CompositeIterator peek:Object children:Iterator subIterator: ComponentIterator ComponentEnumerator( head:Object, components:List, visited:Set) hasNext():bool next():Object

LeafIterator

LeafEnumerator( head:Object,visited:Set) hasNext():bool next():Object

Figure 28.2
Une premire conception pour une famille dnumrateurs par itration.

Les classes emploient les noms de mthodes hasNext() et next() pour que la classe ComponentIterator implmente linterface Iterator du package java.util. La conception montre que les constructeurs de classe dnumration acceptent un objet parcourir par itration. Dans la pratique, cet objet sera un composite, de machines ou de processus, par exemple. La conception utilise aussi une variable visited pour garder trace des nuds dj numrs. Cela nous vite dentrer dans

306

Partie V

Patterns dextension

une boucle innie lorsquun composite a des cycles. Le code pour ComponentIterator au sommet de la hirarchie aura nalement lapparence suivante :
package com.oozinoz.iterator; import java.util.*; public abstract class ComponentIterator implements Iterator { protected Object head; protected Set visited; protected boolean returnInterior = true; public ComponentIterator(Object head, Set visited) { this.head = head; this.visited = visited; } public void remove() { throw new UnsupportedOperationException( "ComponentIterator.Remove"); } }

Cette classe laisse la plus grande part du travail difcile ses sous-classes. Dans la sous-classe CompositeIterator, nous pouvons anticiper le besoin pour un itrateur de liste dnumrer les enfants dun nud composite. Cest lnumration note 1 dans la Figure 28.1, reprsente par la variable children dans la Figure 28.2. Les composites ont galement besoin dun numrateur pour lnumration note 2 dans la gure. La variable subIterator dans la Figure 28.2 rpond ce besoin. Le constructeur de la classe CompositeEnumerator peut initialiser lnumrateur denfants de la manire suivante :
public CompositeIterator( Object head, List components, Set visited) { super(head, visited); children = components.iterator(); }

Lorsque nous commenons la traverse dun composite, nous savons que le premier nud retourner est le nud de tte (marqu H dans la Figure 28.1). Ainsi, le code pour la mthode next() dune classe CompositeIterator pourrait tre comme suit :
public Object next() { if (peek != null) { Object result = peek;

Chapitre 28

ITERATOR

307

peek = null; return result; } if (!visited.contains(head)) { visited.add(head); return head; } return nextDescendant(); }

La mthode next() utilise lensemble visit (Set visited) pour enregistrer si lnumrateur a dj retourn le nud de tte. Sil a retourn la tte dun composite, la mthode nextDescendant() doit trouver le nud suivant. A tout moment, la variable subIterator peut se trouver en cours de processus dnumration dun enfant qui est lui-mme un nud composite. Si cet numrateur est actif, la mthode next() de la classe CompositeIterator peut "dplacer" le sous-itrateur. Si celui-ci ne peut se dplacer, le code doit mettre le prochain lment dans la liste denfants, obtenir un nouveau sous-itrateur pour lui, et dplacer cet numrateur. Le code de la mthode nextDescendant() montre cette logique :
protected Object nextDescendant() { while (true) { if (subiterator != null) { if (subiterator.hasNext()) return subiterator.next(); } if (!children.hasNext()) return null; ProcessComponent pc = (ProcessComponent) children.next(); if (!visited.contains(pc)) { subiterator = pc.iterator(visited); } }

Cette mthode introduit la premire contrainte que nous avons rencontre concernant le type dobjets que nous pouvons numrer : le code requiert que les enfants dans un composite implmentent une mthode iterator(:Set). Considrez un exemple dune structure composite, telle que la hirarchie ProcessComponent que le Chapitre 5 a introduite. La Figure 28.3 illustre la hirarchie de composites de processus quOozinoz utilise pour modliser le ux de processus de fabrication qui produit les divers types dartices.

308

Partie V

Patterns dextension

Figure 28.3
Le ux des processus de fabrication dOozinoz est constitu de composites.
ProcessComponent

ProcessComposite

ProcessStep

ProcessAlternation

ProcessSequence

La mthode next() de la classe CompositeIterator doit numrer les nuds de chaque enfant qui appartient un objet composite. Nous devons faire en sorte que la classe de lenfant implmente une mthode iterator(:Set) que le code de next() puisse utiliser. La Figure 28.2 illustre la relation qui unit les classes et les interfaces. Pour actualiser la hirarchie de ProcessComponent an de pouvoir la parcourir, nous devons prvoir une mthode iterator():
public ComponentIterator iterator() { return iterator(new HashSet()); } public abstract ComponentIterator iterator(Set visited);

La classe ProcessComponent est abstraite et la mthode iterator(:Set) doit tre implmente par les sous-classes. Pour la classe ProcessComposite, le code se prsenterait comme suit :
public ComponentIterator iterator(Set visited) { return new CompositeIterator(this, subprocesses, visited); }

Voici limplmentation de iterator() dans la classe ProcessStep:


public ComponentIterator iterator(Set visited) { return new LeafIterator(this, visited); }

Avec ces petits ajouts en place dans la hirarchie ProcessComponent, nous pouvons maintenant crire du code pour numrer les nuds dun composite de processus.

Chapitre 28

ITERATOR

309

Exercice 28.3 Quel pattern appliquez-vous pour permettre aux classes dune hirarchie ProcessComponent dimplmenter iterator() an de crer des instances dune classe ditrateur approprie ?

buildInnerShell

make:ProcessSequence

inspect

reworkOrFinish:Alternation

:ProcessSequence

disassemble

finish

Figure 28.4
Le ux de processus de fabrication de bombes ariennes est un composite cyclique. Chaque nud feuille dans ce diagramme est une instance de ProcessStep. Les autres nuds sont des instances de ProcessComposite.

La Figure 28.4 illustre le modle objet dun ux de processus typique chez Oozinoz. Le court programme qui suit numre tous les nuds de ce modle :
package app.iterator.process; import com.oozinoz.iterator.ComponentIterator; import com.oozinoz.process.ProcessComponent; import com.oozinoz.process.ShellProcess; public class ShowProcessIteration {

310

Partie V

Patterns dextension

public static void main(String[] args) { ProcessComponent pc = ShellProcess.make(); ComponentIterator iter = pc.iterator(); while (iter.hasNext()) System.out.println(iter.next()); } }

Lexcution de ce programme afche les informations suivantes :


Fabriquer une bombe arienne Crer la coque interne Contrler Retraiter la coque interne, ou finir la bombe Retraiter Dsassembler Terminer: ajouter la charge de propulsion, insrer le dispositif dallumage, et emballer

Les tapes numres sont celles dnies par la classe ShellProcess. Notez que, dans le modle objet, ltape qui suit "Dsassembler" est "Fabriquer". Le rsultat en sortie omet cela car lnumration voit quelle a dj numr cette tape dans la premire ligne du rsultat.
Ajout dun niveau de profondeur un numrateur

La sortie de ce programme pourrait tre plus claire si nous prvoyions chaque tape conformment sa profondeur dans le modle. Nous pouvons dnir la profondeur dun numrateur de feuille comme tant 0 et celle dun numrateur de composite comme tant 1 plus la profondeur de son sous-itrateur. Nous pouvons dclarer getDepth() abstraite dans la super-classe ComponentIterator de la manire suivante :
public abstract int getDepth();

Le code pour la mthode getDepth() dans la classe LeafIterator se prsente comme suit :
public int getDepth() { return 0; }

Le code pour CompositeIterator.getDepth() est :


public int getDepth() { if (subiterator != null) return subiterator.getDepth() + 1; return 0; }

Chapitre 28

ITERATOR

311

Le programme suivant produit une sortie plus lisible :


package app.iterator.process; import com.oozinoz.iterator.ComponentIterator; import com.oozinoz.process.ProcessComponent; import com.oozinoz.process.ShellProcess; public class ShowProcessIteration2 { public static void main(String[] args) { ProcessComponent pc = ShellProcess.make(); ComponentIterator iter = pc.iterator(); while (iter.hasNext()) { for (int i = 0; i < 4 * iter.getDepth(); i++) System.out.print( ); System.out.println(iter.next()); } } }

La sortie du programme est :


Fabriquer une bombe arienne Crer la coque interne Contrler Retraiter la coque interne, ou finir la bombe Retraiter Dsassembler Terminer: ajouter la charge de propulsion, insrer le dispositif dallumage, et emballer

Une autre amlioration que nous pouvons apporter la hirarchie ComponentIterator est de ne permettre que lnumration des feuilles dun composite.
Enumration des feuilles

Supposez que nous voulions permettre une numration de ne retourner que les feuilles. Cela pourrait tre utile si nous tions intresss par des attributs ne sappliquant qu cette catgorie de nuds, tel le temps requis par une tape procdurale. Nous pourrions ajouter un champ returnInterior la classe ComponentIterator an denregistrer si les nuds intrieurs (non feuilles) devraient ou non tre retourns par lnumration :
protected boolean returnInterior = true; public boolean shouldShowInterior() { return returnInterior; } public void setShowInterior(boolean value) { returnInterior = value; }

312

Partie V

Patterns dextension

Dans la mthode nextDescendant() de la classe CompositeIterator, nous aurons besoin de retransmettre cet attribut lorsque nous crerons une nouvelle numration pour un enfant de nud composite :
protected Object nextDescendant() { while (true) { if (subiterator != null) { if (subiterator.hasNext()) return subiterator.next(); } if (!children.hasNext()) return null; ProcessComponent pc = (ProcessComponent) children.next(); if (!visited.contains(pc)) { subiterator = pc.iterator(visited); subiterator.setShowInterior( shouldShowInterior()); } } }

Il nous faudra aussi modier la mthode next() de la classe CompositeEnumerator. Le code actuel est le suivant :
public Object next() { if (peek != null) { Object result = peek; peek = null; return result; } if (!visited.contains(head)) { visited.add(head); } return nextDescendant(); }

Exercice 28.4 Modiez la mthode next() de la classe CompositeIterator pour respecter la valeur du champ returnInterior. La cration dun itrateur, ou dun numrateur, pour un nouveau type de collection peut reprsenter une certaine somme de travail. Lavantage qui en rsultera sera quil sera aussi simple de travailler avec votre collection quavec les bibliothques de classes Java.

Chapitre 28

ITERATOR

313

Rsum
Lobjectif du pattern ITERATOR est de permettre un client daccder en squence aux lments dune collection. Les classes de collection dans les bibliothques Java offrent un support avanc pour travailler avec des collections, dont la gestion ditrations, ou lnumration. Lorsque lon cre un nouveau type de collection, on lui associe souvent un itrateur. Un composite spcique un domaine est un exemple courant de nouveau type de collection. Le support de la boucle for, gnrique comme tendu, amliorera la visibilit de votre code. Vous pouvez concevoir un itrateur relativement gnrique que vous pourrez ensuite appliquer une varit de hirarchies de composites. Lorsque vous instanciez un itrateur, vous devez vous demander si la collection peut changer pendant que vous en numrez les lments. Dans une application monothread, il y a peu de chances que cela se produise, mais dans une application multithread, vous devrez vous assurer que laccs une collection est bien synchronis. Pour numrer des lments en toute scurit dans un tel environnement, vous pouvez synchroniser laccs aux collections en verrouillant un objet mutex. Vous pouvez bloquer tous les accs au cours de lnumration, ou brivement pendant le clonage dune collection. Avec une conception correcte, vous pouvez assurer une scurit inter-threads aux clients de votre code ditrateur.

29
VISITOR
Pour tendre une hirarchie de classes, normalement vous ajoutez simplement des mthodes qui fournissent le comportement souhait. Il peut arriver nanmoins que ce comportement ne soit pas cohrent avec la logique du modle objet existant. Il se peut aussi que vous nayez pas accs au code existant. Dans de telles situations, il peut tre impossible dtendre le comportement de la hirarchie sans modier ses classes. Le pattern VISITOR permet justement au dveloppeur dune hirarchie dintgrer un support pour les cas o dautres dveloppeurs voudraient tendre son comportement. A linstar de INTERPRETER, VISITOR est le plus souvent plac au-dessus de COMPOSITE. Vous pourriez donc vouloir rviser COMPOSITE car nous nous y rfrerons dans ce chapitre. Lobjectif du pattern VISITOR est de vous permettre de dnir une nouvelle opration pour une hirarchie sans changer ses classes.

Application de VISITOR
Le pattern VISITOR permet, avec un peu de prvoyance lors du dveloppement dune hirarchie de classes, douvrir la voie une varit illimite dextensions pouvant tre apportes par un dveloppeur nayant pas accs au code source. Voici comment lappliquer :
m

Ajoutez une opration accept() certaines ou toutes les classes dune hirarchie. Chaque implmentation de cette mthode acceptera un argument dont le type sera une interface que vous crerez.

316

Partie V

Patterns dextension

Crez une interface avec un ensemble doprations partageant un nom commun, habituellement visit, mais possdant des types darguments diffrents. Dclarez une de ces oprations pour chaque classe de la hirarchie dont vous autorisez lextension.

La Figure 29.1 illustre le diagramme de classes de la hirarchie MachineComponent modie pour supporter VISITOR.

interface MachineVisitor MachineComponent visit(:Machine) accept(:MachineVisitor) visit(:MachineComposite)

MachineComposite

Machine

accept(:MachineVisitor)

accept(:MachineVisitor)

Fuser

Mixer

...

Figure 29.1
Pour intgrer le support de VISITOR la hirarchie MachineComponent, ajoutez les mthodes accept() et linterface MachineVisitor prsentes dans ce diagramme.

Ce diagramme nexplique pas comment VISITOR fonctionne, ce qui est lobjet de la prochaine section. Il montre simplement certains des principes vous permettant dappliquer ce pattern.

Chapitre 29

VISITOR

317

Notez que toutes les classes du diagramme de MachineComponent implmentent une mthode accept(). VISITOR nimpose pas toutes les classes de la hirarchie de possder leur propre implmentation de cette mthode. Nanmoins, comme nous le verrons, toutes celles qui limplmentent doivent apparatre sous la forme dun argument dans une mthode visit() dclare dans linterface MachineVisitor. La mthode accept() de la classe MachineComponent est abstraite. Les deux sousclasses implmentent cette mthode en utilisant exactement le mme code :
public void accept(MachineVisitor v) { v.visit(this); }

Cette mthode tant identique dans les classes Machine et MachineComposite, vous pourriez vouloir la remonter dans la classe abstraite MachineComponent. Sachez toutefois que le compilateur voit une diffrence. Exercice 29.1 Quelle diffrence un compilateur Java discerne-t-il entre les mthodes accept() des classes Machine et MachineComposite? Ne regardez pas la solution avant davoir bien rchi car cette diffrence est essentielle pour comprendre VISITOR.

b Les solutions des exercices de ce chapitre sont donnes dans lAnnexe B.

Linterface MachineVisitor impose aux implmenteurs de dnir des mthodes pour "visiter" les machines et les composites de machines :
package com.oozinoz.machine; public interface MachineVisitor { void visit(Machine m); void visit(MachineComposite mc); }

Les mthodes accept() de MachineComponent combines linterface MachineVisitor invitent les dveloppeurs ajouter de nouvelles oprations la hirarchie.

318

Partie V

Patterns dextension

Un VISITOR ordinaire
Imaginez que vous participiez au dveloppement de la toute dernire unit de production dOozinoz Dublin en Irlande. Les dveloppeurs qui sont sur place ont cr un modle objet pour la composition des machines et lont rendu accessible sous la forme de la mthode dublin() statique de la classe OozinozFactory. Pour afcher ce composite, ils ont cr une classe MachineTreeModel an dadapter les informations du modle aux exigences dun objet JTree (le code de MachineTreeModel se trouve dans le package com.oozinoz.dublin). Lafchage des machines de lunit de production demande de crer une instance de MachineTreeModel partir du composite de lunit et denvelopper ce modle dans des composants Swing :
package app.visitor; import javax.swing.JScrollPane; import javax.swing.JTree; import com.oozinoz.machine.OozinozFactory; import com.oozinoz.ui.SwingFacade; public class ShowMachineTreeModel { public ShowMachineTreeModel() { MachineTreeModel model = new MachineTreeModel( OozinozFactory.dublin()); JTree tree = new JTree(model); tree.setFont(SwingFacade.getStandardFont()); SwingFacade.launch( new JScrollPane(tree), " Une nouvelle unit de production Oozinoz"); } public static void main(String[] args) { new ShowMachineTreeModel(); } }

Ce programme afche le rsultat illustr Figure 29.2. Nombre de comportements utiles sont possibles pour un composite de machines. Par exemple, supposez que vous ayez besoin de trouver une certaine machine dans le modle. Pour ajouter cette possibilit sans modier la hirarchie MachineComponent, vous pouvez crer une classe FindVisitor, comme le montre la Figure 29.3.

Chapitre 29

VISITOR

319

Figure 29.2
Lapplication GUI prsente la composition des machines de la nouvelle unit de production irlandaise.

Figure 29.3
La classe FindVisitor ajoute une opration find() la hirarchie MachineComponent.

interface MachineVisitor

visit(:Machine) visit(:MachineComposite)

FindVisitor soughtId:int found:MachineComponent find( mc:MachineComponent, id:int):MachineComponent visit(:Machine) visit(:MachineComposite)

320

Partie V

Patterns dextension

Les mthodes visit() ne retournent pas dobjet, aussi la classe FindVisitor enregistre-t-elle ltat dune recherche dans sa variable dinstance found:
package app.visitor; import com.oozinoz.machine.*; import java.util.*; public class FindVisitor implements MachineVisitor { private int soughtId; private MachineComponent found; public MachineComponent find( MachineComponent mc, int id) { found = null; soughtId = id; mc.accept(this); return found; } public void visit(Machine m) { if (found == null && m.getId() == soughtId) found = m; } public void visit(MachineComposite mc) { if (found == null && mc.getId() == soughtId) { found = mc; return; } Iterator iter = mc.getComponents().iterator(); while (found == null && iter.hasNext()) ((MachineComponent) iter.next()).accept(this); } }

Les mthodes visit() examinent la variable found pour que la traverse de larbre se termine aussitt que le composant recherch a t trouv. Exercice 29.2 Ecrivez un programme qui trouve et afche lobjet StarPress:3404 dans linstance de MachineComponent retourne par OozinozFactory.dublin().

Chapitre 29

VISITOR

321

La mthode find() ne se proccupe pas de savoir si lobjet MachineComponent quelle reoit est une instance de Machine ou de MachineComposite. Elle invoque simplement accept() qui invoque son tour visit(). Notez que la boucle dans la mthode visit(:MachineComposite) ne se soucie pas non plus de savoir si un composant enfant est une instance de Machine ou de MachineComposite. La mthode visit() invoque simplement lopration accept() de chaque composant. La mthode qui sexcute comme rsultat de cette invocation dpend du type de lenfant. La Figure 29.4 prsente une squence typique dappels de mthodes.

:FindVisitor

:MachineComposite

:Machine

visit(dublin)

accept(this)

visit(this)

accept(this)

visit(this)

Figure 29.4
Un objet FindVisitor invoque une opration accept() pour dterminer quelle mthode visit() excuter.

Lorsque la mthode visit(:MachineComposite) sexcute, elle invoque donc lopration accept() de chaque enfant du composite. Un enfant rpond en invoquant lopration visit() de lobjet Visitor. Comme le montre la Figure 29.4, cet aller-retour entre lobjet Visitor et lobjet qui reoit linvocation de accept() permet de rcuprer le type de ce dernier. Cette technique, qualie de double

322

Partie V

Patterns dextension

dispatching, garantit que la mthode visit() approprie de la classe Visitor est excute. Le double dispatching dans VISITOR permet de crer des classes visiteur avec des mthodes qui sont spciques aux divers types de la hirarchie visite. Vous pouvez ajouter pratiquement nimporte quel comportement au moyen de ce pattern, comme si vous contrliez le code source. Comme autre exemple, considrez un visiteur qui trouve toutes les machines les nuds feuille dun composant-machine :
package app.visitor; import com.oozinoz.machine.*; import java.util.*; public class RakeVisitor implements MachineVisitor { private Set leaves; public Set getLeaves(MachineComponent mc) { leaves = new HashSet(); mc.accept(this); return leaves; } public void visit(Machine m) { // Exercice ! } public void visit(MachineComposite mc) { // Exercice ! } }

Exercice 29.3 Compltez le code de la classe RakeVisitor pour collecter les feuilles (leave) dun composant-machine.

Un court programme peut trouver les feuilles dun composant-machine et les afcher :
package app.visitor;

Chapitre 29

VISITOR

323

import com.oozinoz.machine.*; import java.io.*; import com.oozinoz.filter.WrapFilter; public class ShowRakeVisitor { public static void main(String[] args) throws IOException { MachineComponent f = OozinozFactory.dublin(); Writer out = new PrintWriter(System.out); out = new WrapFilter(new BufferedWriter(out), 60); out.write( new RakeVisitor().getLeaves(f).toString()); out.close(); } }

Ce programme utilise un ltre de passage la ligne pour produire son rsultat :


[StarPress:3401, Fuser:3102, StarPress:3402, Mixer:3202, Fuser:3101, StarPress:3403, ShellAssembler:1301, ShellAssembler:2301, Mixer:1201, StarPress:2401, Mixer:3204, Mixer:3201, Fuser:1101, Fuser:2101, ShellAssembler:3301, ShellAssembler:3302, StarPress:1401, Mixer:3203, Mixer:2202, StarPress:3404, Mixer:2201, StarPress:2402]

Les classes FindVisitor et RakeVisitor ajoutent toutes deux un nouveau comportement la hirarchie MachineComponent et semblent fonctionner correctement. Cependant, le risque dutiliser des visiteurs est quils demandent de comprendre la hirarchie qui est tendue. Un changement dans cette hirarchie pourrait rendre votre visiteur inoprant, ou vous pourriez ne pas saisir correctement le fonctionnement de la hirarchie. En particulier, vous pourriez avoir grer des cycles si le composite que vous visitez ne les empche pas.

Cycles et VISITOR
La hirarchie ProcessComponent quutilise Oozinoz pour modliser ses ux de processus est une autre structure composite laquelle il peut tre utile dintgrer un support de VISITOR. Contrairement aux composites de machines, il est naturel pour les ux de processus de contenir des cycles, et les objets visiteurs doivent viter de gnrer des boucles innies lorsquils parcourent des composites de processus. La Figure 29.5 prsente la hirarchie ProcessComponent.

324

Partie V

Patterns dextension

ProcessComponent ProcessComponent(name:String) accept(:ProcessVisitor) getName():String

interface ProcessVisitor visit(:ProcessAlternation) visit(:ProcessSequence) visit(:ProcessStep)

ProcessComposite getChildren():List

ProcessStep accept(:ProcessVisitor)

ProcessAlternation

ProcessSequence

accept(:ProcessVisitor)

accept(:ProcessVisitor)

Figure 29.5
A linstar de la hirarchie MachineComponent, la hirarchie ProcessComponent peut intgrer un support de VISITOR.

Supposez que vous vouliez afcher un composant-processus dans un format indent. Dans le Chapitre 28, consacr au pattern ITERATOR, nous avons utilis un itrateur pour afcher les tapes dun ux de processus, que voici :
Fabriquer une bombe arienne Crer une coque interne Contrler Retraiter la coque interne, ou finir la bombe Retraiter Dsassembler Terminer : ajouter la charge de propulsion, insrer le dispositif dallumage, et emballer

Retravailler une bombe implique de la dsassembler et de la rassembler. Ltape qui suit Dsassembler est Fabriquer une bombe arienne. Lafchage prcdent ne rpte pas cette tape car litrateur voit quelle est dj apparue une fois. Il serait nanmoins plus informatif de la faire apparatre de nouveau en indiquant que le

Chapitre 29

VISITOR

325

processus entre ici dans un cycle. Il serait galement utile dindiquer quels composites constituent des alternances par opposition des squences. Pour crer un programme dafchage des processus, vous pourriez crer une classe visiteur qui initialise un objet StringBuilder et ajoute ce tampon les nuds dun composant-processus mesure quils sont visits. Pour indiquer quune tape dun composite est une alternance, le visiteur pourrait faire prcder son nom dun point dinterrogation (?). Pour indiquer quune tape est dj apparue, il pourrait la faire suivre de trois points de suspension (). Avec ces changements, le processus de fabrication dune bombe arienne ressemblerait ce qui suit :
Fabriquer une bombe arienne Crer une coque interne Contrler ?Retraiter la coque interne, ou finir la bombe Retraiter Dsassembler Fabriquer une bombe arienne... Terminer : ajouter la charge de propulsion, insrer le dispositif dallumage, et emballer

Un visiteur de composant-processus doit tenir compte des cycles, mais cela est facile mettre en uvre en utilisant un objet Set qui garde trace des nuds visits. Le code de cette classe dbuterait ainsi :
package app.visitor; import java.util.List; import java.util.HashSet; import java.util.Set; import com.oozinoz.process.*; public class PrettyVisitor implements ProcessVisitor { public static final String INDENT_STRING = " "; private StringBuffer buf; private int depth; private Set visited; public StringBuffer getPretty(ProcessComponent pc) { buf = new StringBuffer(); visited = new HashSet(); depth = 0; pc.accept(this); return buf; } protected void printIndentedString(String s) { for (int i = 0; i < depth; i++) buf.append(INDENT_STRING);

326

Partie V

Patterns dextension

buf.append(s); buf.append("\n"); } // ... mthodes visit()... }

Cette classe utilise une mthode getPretty() pour initialiser les variables dune instance et lancer lalgorithme visiteur. La mthode printIndentedString() gre lindentation des tapes mesure que lalgorithme explore plus avant un composite. Lorsquil visite un objet ProcessStep, le code afche simplement le nom de ltape :
public void visit(ProcessStep s) { printIndentedString(s.getName()); }

Vous avez peut-tre remarqu dans la Figure 29.5 que la classe ProcessComposite nimplmente pas de mthode accept() mais que ses sous-classes le font. Visiter une alternance ou une squence de processus requiert une logique identique, que voici :
public void visit(ProcessAlternation a) { visitComposite("?", a); } public void visit(ProcessSequence s) { visitComposite("", s); } protected void visitComposite( String prefix, ProcessComposite c) { if (visited.contains(c)) { printIndentedString(prefix + c.getName() + ""); } else { visited.add(c); printIndentedString(prefix + c.getName()); depth++; List children = c.getChildren(); for (int i = 0; i < children.size(); i++) { ProcessComponent child = (ProcessComponent) children.get(i); child.accept(this); } depth--; } }

Chapitre 29

VISITOR

327

La diffrence entre visiter une alternance et visiter une squence est que la premire utilise un point dinterrogation comme prxe. Dans les deux cas, si lalgorithme a dj visit le nud, nous afchons son nom suivi de trois points de suspension. Sinon, nous lajoutons une collection de nuds visits, afchons son prxe un point dinterrogation ou rien et "acceptons" ses enfants. Chose courante avec le pattern VISITOR, le code recourt au polymorphisme pour dterminer si les nuds enfant sont des instances des classes ProcessStep, ProcessAlternation ou ProcessSequence. Un court programme peut maintenant afcher le ux de processus :
package app.visitor; import com.oozinoz.process.ProcessComponent; import com.oozinoz.process.ShellProcess; public class ShowPrettyVisitor { public static void main(String[] args) { ProcessComponent p = ShellProcess.make(); PrettyVisitor v = new PrettyVisitor(); System.out.println(v.getPretty(p)); } }

Ce programme afche le rsultat suivant :


Fabriquer une bombe arienne Crer la coque interne Contrler ?Retraiter la coque interne, ou finir la bombe Retraiter Dsassembler Fabriquer une bombe arienne Terminer : ajouter la charge de propulsion, insrer le dispositif dallumage, et emballer

Ce rsultat est plus informatif que celui obtenu en parcourant simplement le modle de processus. Le point dinterrogation qui apparat signale que les tapes de ce composite sont des alternances. De plus, le fait dafcher ltape Fabriquer une bombe arienne une seconde fois, suivie de points de suspension, est plus clair que de simplement omettre une tape qui se rpte. Les dveloppeurs de la hirarchie ProcessComponent intgrent le support de VISITOR en y incluant des mthodes accept() et en dnissant linterface ProcessVisitor. Ils ont tous conscience de la ncessit dviter les boucles innies lors de litration sur des ux de processus. Comme le montre la classe

328

Partie V

Patterns dextension

PrettyVisitor, ils doivent aussi avoir conscience de lventualit de cycles dans les composants-processus. Ils pourraient rduire le risque derreur en prvoyant un support des cycles dans le cadre de leur support de VISITOR. Exercice 29.4 Comment les dveloppeurs de ProcessComponent peuvent-ils intgrer la hirarchie la fois un support des cycles et un support de VISITOR?

Risques de VISITOR
VISITOR est un pattern sujet controverse. Certains dveloppeurs vitent systmatiquement de lappliquer, tandis que dautres dfendent son utilisation et suggrent des moyens de le renforcer, bien que ces suggestions augmentent gnralement la complexit du code. Le fait est que ce pattern peut donner lieu de nombreux problmes de conception. La fragilit de VISITOR est perceptible dans les exemples de ce chapitre. Par exemple, les dveloppeurs de la hirarchie MachineComponent ont choisi de faire une distinction entre les nuds Machine et les nuds MachineComposite, mais ils ne diffrencient pas les sous-classes de Machine. Si vous aviez besoin de distinguer diffrents types de machines dans votre visiteur, il vous faudrait procder une vrication du type ou employer dautres techniques pour dterminer quel type de machine une mthode visit() a reue. Vous pensez peut-tre que les dveloppeurs auraient d inclure tous les types de machines ainsi quune mthode visit(:Machine) gnrique dans linterface visiteur. Mais de nouveaux types de machines apparaissent rgulirement et cette solution ne serait donc pas plus robuste. VISITOR ne reprsente pas toujours une bonne option selon les changements susceptibles dintervenir. Si la hirarchie est stable et que les comportements associs changent, ce choix peut convenir. Mais si les comportements sont stables et que la hirarchie change, cela vous obligera actualiser les visiteurs existants pour quils puissent supporter les nouveaux types de nuds. Un autre exemple de faiblesse apparat dans la hirarchie ProcessComponent. Les dveloppeurs savent que les cycles reprsentent un danger li aux modles de ux

Chapitre 29

VISITOR

329

de processus. Comment peuvent-ils communiquer ce risque aux dveloppeurs de visiteurs ? Ces difcults sont rvlatrices du problme fondamental de ce pattern, savoir que lextension des comportements dune hirarchie demande normalement une connaissance experte de sa conception. Si vous ne possdez pas cette expertise, vous risquez de tomber dans un pige, comme celui de ne pas viter les cycles dun ux de processus. Vous risquez de crer des dpendances dangereuses qui ne rsisteront pas aux changements de la hirarchie. La distribution de lexpertise et du contrle du code source peut rendre VISITOR dangereux appliquer. Un cas classique o ce pattern semble bien fonctionner sans causer de problmes subsquents est celui des analyseurs syntaxiques des langages informatiques. Leurs dveloppeurs sarrangent souvent pour que lanalyseur (parser) cre un arbre syntaxique abstrait, cest--dire une structure qui organise le texte en entre en fonction de la grammaire du langage. Vous pourriez dvelopper une varit de comportements pour accompagner ces arbres, et le pattern VISITOR reprsente une approche efcace permettant cela. Dans ce cas classique, il ny a quasiment pas de comportements, voire aucun, dans la hirarchie visite. La responsabilit de concevoir les comportements revient aux visiteurs, vitant ainsi la distribution de responsabilit observe dans les exemples de ce chapitre. Comme nimporte quel autre pattern, VISITOR nest jamais ncessaire, sinon il apparatrait partout o il le serait. En outre, il existe souvent des alternatives offrant des conceptions plus robustes. Sachez quil est le plus performant dans les conditions suivantes :
m m

Lensemble des types de nuds est stable. Un changement courant est lajout de nouvelles fonctions qui sappliquent aux diffrents nuds.

A noter que les nouvelles fonctions devraient toucher tous les types de nuds. Exercice 29.5 Enumrez deux alternatives lemploi de VISITOR dans les hirarchies de machines et de processus dOozinoz.

330

Partie V

Patterns dextension

Rsum
Le pattern VISITOR permet de dnir une nouvelle opration pour une hirarchie sans avoir modier ses classes. Son application implique de dnir une interface pour les visiteurs et dajouter des mthodes accept() la hirarchie qui sera appele par un visiteur. Ces mthodes renvoient leur appel au visiteur en utilisant une technique de double dispatching, laquelle fait quune mthode visit() sappliquera au type dobjet correspondant dans la hirarchie. Le dveloppeur dun visiteur doit connatre certaines, voire toutes les subtilits de conception de la hirarchie visite. En particulier, les visiteurs doivent avoir connaissance des cycles susceptibles de survenir dans le modle objet visit. Ce genre de difcults pousse certains dveloppeurs renoncer utiliser VISITOR, et lui prfrer dautres alternatives. La dcision demployer ce pattern devrait dpendre de votre philosophie de conception, de votre quipe et des spcicit de votre application.

VI
Annexes

A
Recommandations
Si vous avez lu ce livre jusquici, flicitations ! Si vous avez effectu tous les exercices, bravo ! Vous avez dvelopp une bonne connaissance pratique des patterns de conception. Cette annexe vous donne des recommandations pour pousser plus loin votre apprentissage.

Tirer le meilleur parti du livre


Si vous navez pas accompli les exercices du livre, vous ntes pas le seul ! Nous sommes tous trs occups, et il est tentant de simplement rchir au problme puis de consulter la solution. Cette dmarche est assez commune, mais ce qui est dommage, cest que vous avez la possibilit de devenir un dveloppeur hors norme. Refaites les exercices un un et srieusement, en vous reportant la solution uniquement lorsque vous pensez avoir trouv une bonne rponse ou si vous tes bloqu. Faites-le maintenant. Ne vous dites pas que vous aurez davantage de temps plus tard. En exerant vos connaissances frachement acquises sur les patterns, vous acquerrez lassurance dont vous avez besoin pour commencer les appliquer dans votre travail. Nous vous suggrons galement de tlcharger le code disponible ladresse www.oozinoz.com et de reproduire les exemples du livre sur votre systme. Parvenir excuter ce code vous apportera plus de conance que si vous vous contentez de travailler sur papier. Vous pouvez aussi laborer de nouveaux exercices. Vous voudrez peut-tre trouver de nouvelles faons de combiner des ltres DECORATOR, ou implmenter un ADAPTER de donnes qui afche des informations dun domaine familier.

334

Partie VI

Annexes

A mesure que vous vous familiarisez avec les patterns, vous devriez commencer comprendre les exemples classiques de leur utilisation. Vous commencerez galement identier les endroits o il peut tre appropri dappliquer des patterns dans votre code.

Connatre ses classiques


Les patterns rendent souvent une conception plus robuste. Cette ide nest pas nouvelle, aussi nest-il pas surprenant que de nombreux patterns soient intgrs aux bibliothques de classes Java. Si vous pouvez reprer un pattern dans le corps dun programme, vous pouvez saisir la conception et la communiquer dautres dveloppeurs qui comprennent galement les patterns. Par exemple, si un dveloppeur comprend comment fonctionne DECORATOR, cela a du sens dexpliquer que les ux Java sont des dcorateurs. Voici un test pour contrler votre comprhension des exemples classiques dutilisation des patterns, qui apparaissent dans Java et ses bibliothques .
m m m

Comment les GUI contrlent-elles lemploi du pattern OBSERVER? Pourquoi les menus emploient-ils souvent le pattern COMMAND? En quoi les drivers constituent-ils un bon exemple du pattern BRIDGE? Chaque driver est-il une instance du pattern ADAPTER? Que signie le fait de dire que les ux Java utilisent le pattern DECORATOR? Pourquoi le pattern PROXY est-il essentiel pour la conception de RMI ? Si le tri est un bon exemple du pattern TEMPLATE METHOD, quelle tape de lalgorithme nest pas spcie initialement ?

m m m

Lidal est de rpondre ces questions sans vous aider du livre. Un bon exercice est de mettre vos rponses par crit et de les partager avec des collgues.

Appliquer les patterns


Devenir un meilleur dveloppeur est ce qui incite le plus souvent apprendre les patterns de conception. Leur application peut se faire de deux manires : lorsque vous ajoutez du code ou via une refactorisation. Si une partie de votre code est complexe et difcile grer, vous pourriez lamliorer en le refactorisant et en

Annexe A

Recommandations

335

utilisant un pattern. Avant de vous lancer dans un tel projet, assurez-vous que cela en vaille la peine. Veillez aussi crer une suite de tests automatiss pour le code que vous rorganisez. Supposez maintenant que vous compreniez bien les patterns tudis et soyez dtermin les utiliser prudemment et de manire approprie. Comment trouvez-vous des opportunits ? En fait, des occasions se prsentent assez frquemment. Pour les identier, considrez les points suivants .
m

Votre base de code comporte-t-elle des parties complexes lies ltat dun systme ou de lutilisateur de lapplication ? Si oui, vous pourriez lamliorer en appliquant le pattern STATE. Votre code combine-t-il la slection dune stratgie et lexcution de cette stratgie ? Si oui, vous pourriez lamliorer en appliquant le pattern STRATEGY. Votre client ou analyste vous remet-il des organigrammes qui donnent lieu du code difcile comprendre ? Si oui, vous pouvez appliquer le pattern INTERPRETER, en faisant de chaque nud de lorganigramme une instance dune classe de la hirarchie interprteur. Vous obtiendrez ainsi une traduction directe de lorganigramme en code. Votre code comporte-t-il un composite faible qui nautorise pas ses enfants tre eux-mmes des composites ? Vous pourriez renforcer ce code laide du pattern COMPOSITE. Avez-vous rencontr des erreurs dintgrit relationnelle dans votre modle objet ? Vous pourriez les viter en appliquant le pattern MEDIATOR pour centraliser la modlisation des relations entre les objets. Votre code comporte-t-il des endroits o des clients utilisent les informations dun service pour dcider quelle classe instancier ? Vous pourriez amliorer et simplier ce code en appliquant le pattern FACTORY METHOD.

Connatre les patterns permet de dvelopper un vocabulaire riche dides de conception. Si vous recherchez des opportunits, vous naurez probablement pas attendre longtemps avant de trouver une conception qui peut tre amliore par lapplication dun pattern. Mais ne forcez pas les choses.

336

Partie VI

Annexes

Continuer dapprendre
Cest dj une bonne chose que vous ayez eu lopportunit, la volont et lambition dacqurir et de lire ce livre. Continuez ainsi ! Dterminez combien dheures par semaine vous voulez consacrer votre carrire. Si vous arrivez vous en mnager cinq, cest trs bien. Passez ce temps en dehors de votre bureau, lire des livres et des magazines ou crire des programmes en rapport avec un sujet qui vous intresse. Que cela devienne une habitude aussi rgulire que daller au bureau. Traitez srieusement cet aspect de votre carrire et vous deviendrez un bien meilleur dveloppeur. Vous nen apprcierez ensuite que mieux votre travail. Vous pouvez prendre de nombreuses directions, lessentiel tant de continuer progresser. Considrez lacte dapprendre comme faisant partie de votre plan de carrire, et adonnez-vous aux sujets qui vous passionnent le plus. Pensez aux comptences que vous pouvez acqurir avec le temps, et entreprenez ce quil faut pour cela. Steve.Metsker@acm.org William.Wake@acm.org

B
Solutions
Introduction aux interfaces
Solution 2.1

Une classe abstraite ne contenant aucune mthode non abstraite est semblable une interface du point de vue de son utilit. Notez toutefois les nuances suivantes :
m

Une classe peut implmenter autant dinterfaces que ncessaire mais elle ne peut tendre au plus quune classe abstraite. Une classe abstraite peut avoir des mthodes non abstraites, mais toutes les mthodes dune interface sont abstraites. Une classe abstraite peut dclarer et utiliser des champs alors quune interface ne le peut pas, bien quelle puisse crer des constantes static final. Une classe abstraite peut avoir des mthodes dclares public, protected, private ou sans accs (package). Les mthodes dune interface ont un accs implicitement public. Une classe abstraite peut dnir des constructeurs alors quune interface ne le peut pas.

338

Partie VI

Annexes

Solution 2.2

A. Vrai. Les mthodes dune interface sont toujours abstraites mme sans dclaration explicite. B. Vrai. Les mthodes dune interface sont publiques mme sans dclaration explicite. C. Faux. La visibilit dune interface peut se limiter au package dans lequel elle rside. Dans ce cas, elle est marque public an que les classes externes com.oozinoz.simulation puissent y accder. D. Vrai. Par exemple, les interfaces List et Set tendent toutes deux linterface Collection dans java.util. E. Faux. Une interface sans mthode est appele interface de marquage. Parfois, une mthode haut dans la hirarchie de classe, telle que Object.clone(), nest pas approprie pour toutes les sous-classes. Vous pouvez crer une interface de marquage qui demande aux sous-classes dopter ou non pour une telle stratgie. La mthode clone() sur Object requiert que les sous-classes optent pour la stratgie, en dclarant quelles implmentent linterface de marquage Cloneable. F. Faux. Une interface ne peut dclarer des champs dinstance, bien quelle puisse crer des constantes en dclarant des champs qui sont static et final. G. Faux. Ce peut tre une bonne ide mais une interface ne dispose daucun moyen pour exiger que les classes limplmentant fournissent un certain constructeur.
Solution 2.3

Un tel exemple se produit lorsque des classes peuvent tre enregistres en tant que listeners dvnements. Les classes reoivent des notications pour leur propre compte et non de celui de lappelant. Par exemple, nous pourrions ragir avec MouseListener.mouseDragged() mais avoir un corps vide pour Listener.mouseMoved() pour le mme listener.

ADAPTER
Solution 3.1

Votre solution devrait ressembler au diagramme illustr la Figure B.1.

Annexe B

Solutions

339

Figure B.1
La classe OozinozRocket adapte la classe PhysicalRocket pour rpondre aux besoins dclars dans linterface RocketSim.
PhysicalRocket interface RocketSim getMass():double getThrust():double setSimTime(t:double) PhysicalRocket( burnArea:double, burnRate:double, fuelMass:double, totalMass:double) getBurnTime():double getMass(t:double):double getThrust(t:double):double

OozinozRocket time:double OozinozRocket( burnArea:double, burnRate:double, fuelMass:double, totalMass:double) getMass():double getThrust():double setSimTime(t:double)

Les instances de la classe OozinozRocket peuvent fonctionner en tant quobjets PhysicalRocket ou RocketSim. Le pattern ADAPTER vous permet dadapter les mthodes que vous avez celles dont un client a besoin.
Solution 3.2

Le code pour complter la classe devrait tre :


package com.oozinoz.firework; import com.oozinoz.simulation.*; public class OozinozRocket extends PhysicalRocket implements RocketSim { private double time; public OozinozRocket( double burnArea, double burnRate, double fuelMass, double totalMass) { super(burnArea, burnRate, fuelMass, totalMass); }

340

Partie VI

Annexes

public double getMass() { return getMass(time); } public double getThrust() { return getThrust(time); } public void setSimTime(double time) { this.time = time; } }

Vous trouverez ce code dans le package com.oozinoz.firework du code source compagnon de ce livre.
Solution 3.3

La Figure B.2 illustre une solution.


Skyrocket #simTime:double ... Skyrocket( mass:double, thrust:double burnTime:double) getMass():double getThrust():double setSimTime(t:double) PhysicalRocket

PhysicalRocket( burnArea:double, burnRate:double, fuelMass:double, totalMass:double) getBurnTime():double getMass(t:double):double getThrust(t:double):double

OozinozSkyrocket rocket:PhysicalRocket OozinozRocket( r:PhysicalRocket) getMass():double getThrust():double

Figure B.2
Un objet OozinozSkyrocket est un objet Skyrocket, mais son travail est ralis par transmission des appels un objet PhysicalRocket.

Annexe B

Solutions

341

La classe OozinozSkyrocket est un adaptateur dobjet. Elle tend Skyrocket pour quun objet OozinozSkyrocket puisse fonctionner o un objet Skyrocket est requis.
Solution 3.4

La conception avec adaptateur dobjet utilise par la classe OozinozSkyrocket peut se rvler plus fragile quune approche par adaptateur de classe pour les raisons suivantes :
m

Il ny a aucune spcication de linterface que la classe OozinozSkyrocket fournit. Par consquent, mme si cela na pas t dtect la compilation, Skyrocket pourrait changer dune certaine faon qui pourrait tre source de problmes lors de lexcution. La classe OozinozSkyrocket compte sur la capacit accder la variable simTime de sa classe parent, bien quil ny ait aucune garantie que cette variable sera toujours dclare comme tant protge et aucune certitude quant la signication de ce champ dans la classe Skyrocket. Nous nattendons pas de la part des fournisseurs du code quils scartent de leur conception pour changer le code Skyrocket sur lequel nous nous appuyons, mais nous disposons toutefois dun contrle limit sur ce quils font.

Solution 3.5

Votre code pourrait ressembler lexemple suivant :


package app.adapter; import javax.swing.table.*; import com.oozinoz.firework.Rocket; public class RocketTable extends AbstractTableModel { protected Rocket[] rockets; protected String[] columnNames = new String[] { "Name", "Price", "Apogee" }; public RocketTable(Rocket[] rockets) { this.rockets = rockets; } public int getColumnCount() { return columnNames.length; } public String getColumnName(int i) { return columnNames[i]; } public int getRowCount() { return rockets.length; }

342

Partie VI

Annexes

public Object getValueAt(int row, int col) { switch (col) { case 0: return rockets[row].getName(); case 1: return rockets[row].getPrice(); case 2: return new Double(rockets[row].getApogee()); default: return null; } } }

Linterface TableModel est un bon exemple de lefcacit de prvoir lavance une adaptation. Cette interface, ainsi que son implmentation partielle AbstractTableModel, limite le travail ncessaire pour montrer les objets spciques dans une table de GUI. Votre solution devrait permettre de constater comme il est simple de recourir ladaptation lorsquelle est supporte par une interface.
Solution 3.6
m

Un argument pour. Lorsque lutilisateur clique avec la souris, je dois traduire, ou adapter, lappel Swing rsultant en une action approprie. En dautres termes, lorsque je dois adapter des vnements de GUI linterface de mon application, jemploie les classes dadaptation de Swing. Je ralise une conversion dune interface une autre, cest--dire lobjectif du pattern ADAPTER. Un argument contre. Les classes dadaptation dans Swing sont des stubs : elles nadaptent rien. Vous drivez des sous-classes de ces classes, o vous implmentez alors les mthodes qui doivent raliser quelque chose. Ce sont vos mthodes et votre classe qui appliqueraient ventuellement le pattern ADAPTER. Si "ladaptateur" de Swing avait t nomm disons DefaultMouseListener, cet argument ne pourrait tre avanc.

FACADE
Solution 4.1

Voici quelques diffrences notables entre une dmo et une faade :


m

Une dmo est gnralement une application autonome alors quune faade ne lest gnralement pas.

Annexe B

Solutions

343

m m m m

Une dmo inclut gnralement un chantillon, ce quune faade ne fait pas. Une faade est gnralement congurable, pas une dmo. Une faade est prvue pour tre rutilise, pas une dmo. Une faade est prvue pour tre utilise en production, pas une dmo.

Solution 4.2

La classe JOptionPane est lun des exemples peu nombreux dune faade dans les bibliothques de classes Java. Elle est utilisable en production, congurable et est prvue pour tre rutilise. Avant toute chose, JOptionPane ralise lobjectif du pattern FACADE en fournissant une interface simple qui facilite lemploi dune classe JDialog. Vous pouvez soutenir quune faade simplie un "sous-systme" et que la classe JDialog seule ne remplit pas les conditions pour tre un soussystme. Cest toutefois la richesse des fonctionnalits de cette classe qui donne toute sa valeur une faade. Sun Microsystems incorpore de nombreuses dmos dans son JDK. Ces classes ne font toutefois jamais partie des bibliothques de classes Java. Cest--dire quelles napparaissent pas dans les packages nomms avec le prxe java. Une faade peut appartenir aux bibliothques Java, mais pas les dmos. La classe JOptionPane possde des dizaines de mthodes statiques qui font effectivement delle un utilitaire ainsi quune application de FACADE. Toutefois, strictement parler, elle ne remplit pas les conditions de la dnition dans UML dun utilitaire, laquelle exige que toutes les mthodes soient statiques.
Solution 4.3

Voici quelques points de vue acceptables mais opposs concernant la pnurie de faades dans les bibliothques de classes Java.
m

En tant que dveloppeur Java, vous tes bien avis de dvelopper une profonde connaissance des outils de la bibliothque. Les faades limitent ncessairement la faon dont vous pouvez appliquer nimporte quel systme. Elles seraient une distraction et un lment ventuel de mprise dans les bibliothques dans lesquelles elles apparatraient. Les caractristiques dune faade se situent quelque part entre la richesse dun kit doutils et la spcicit dune application. Crer une faade requiert davoir

344

Partie VI

Annexes

une certaine ide du type dapplications quelle supportera. Il est impossible de prvoir cela compte tenu de limmense diversit du public utilisateur des bibliothques Java.
m

La prsence rare de faades dans les bibliothques Java est un point faible. Lajout de davantage de faades serait fortement utile.

Solution 4.4

Votre rponse devrait sapparenter au diagramme de la Figure B.3.


Figure B.3
Ce diagramme illustre lapplication de calcul de trajectoire restructure en classes se chargeant chacune dune tche.
JPanel

ShowFlight2

PlotPanel

main(:String[])

PlotPanel(nPoint:int, xFunc:Function, yFunc:Function) paintComponent(:Graphics)

UI NORMAL:UI createTitledPanel( title:String, panel:JPanel)

Function f(t:double):double

Notez que createTitledPanel() nest pas une mthode statique. Votre solution at-elle prvu que ces mthodes soient statiques ? Pourquoi ou pourquoi pas ? Le code prsent dans ce livre choisit les mthodes de UI comme tant non statiques pour quune sous-classe de UI puisse les rednir, crant ainsi un kit diffrent pour laborer des interfaces graphiques utilisateur. Pour permettre la disponibilit dune interface utilisateur standard, cette conception se rfre lobjet singleton NORMAL. Voici le code appropri pour la classe UI:
public class UI { public static final UI NORMAL = new UI();

Annexe B

Solutions

345

protected Font font = new Font("Book Antiqua", Font.PLAIN, 18); // beaucoup de choses omises public Font getFont() { return font; } public TitledBorder createTitledBorder(String title) { TitledBorder border = BorderFactory.createTitledBorder( BorderFactory.createBevelBorder( BevelBorder.RAISED), title, TitledBorder.LEFT, TitledBorder.TOP); border.setTitleColor(Color.black); border.setTitleFont(getFont()); return border; } public JPanel createTitledPanel( String title, JPanel in) { JPanel out = new JPanel(); out.add(in); out.setBorder(createTitledBorder(title)); return out; } }

Voyez le Chapitre 17 pour plus dinformations sur la conception de kits de GUI, et le Chapitre 8 qui tudie les singletons.

COMPOSITE
Solution 5.1

Prvoir la classe Composite de faon pouvoir conserver une collection dobjets Component demande quun objet Composite puisse contenir des objets feuilles ou dautres objets composites. En dautres termes, cette conception nous permet de modliser des groupes en tant que collections dautres groupes. Par exemple, nous pouvons dnir les privilges systme dun utilisateur sous forme dune collection de privilges spciques ou dautres groupes de privilges. Nous pourrions aussi dnir une procdure en tant que collection dtapes procdurales et dautres procdures. De telles dnitions sont beaucoup plus souples que la dnition dun composite comme devant tre une collection de feuilles uniquement.

346

Partie VI

Annexes

Si vous nautorisez que des collections de feuilles, les composites ne pourront avoir quun niveau de profondeur.
Solution 5.2

Pour la classe Machine, getMachineCount() devrait ressembler lexemple suivant :


public int getMachineCount() { return 1; }

Le diagramme de classes montre que MachineComposite utilise un objet List pour garder trace de ses composants. Pour compter les machines dans un composite, vous pourriez crire le code suivant :
public int getMachineCount() { int count = 0; Iterator i = components.iterator(); while (i.hasNext()) { MachineComponent mc = (MachineComponent) i.next(); count += mc.getMachineCount(); } return count; }

Si vous utilisez le JDK 1.5, vous pourriez utiliser une boucle for tendue.
Solution 5.3
Mthode Classe Dnition Retourne la somme des comptes pour chaque composant de Component Retourne 1 Retourne true si tous les composants sont actifs Retourne true si cette machine est active Indique tous les composants de tout arrter Arrte cette machine

getMachineCount()

MachineComposite Machine

isCompletelyUp()

MachineComposite Machine

stopAll()

MachineComposite
Machine

Annexe B

Solutions

347

Mthode

Classe

Dnition Cre un set (ensemble), pas une liste ; ajoute les propritaires de tous les composants, puis retourne le set Retourne les propritaires de cette machine Retourne une collection de tous les produits sur les composants-machine Retourne les produits se trouvant sur cette machine

getOwners()

MachineComposite

Machine getMaterial() MachineComposite Machine

Solution 5.4

Le programme afche :
Nombre de machines: 4

Il ny a en fait que trois machines dans plant, mais la machine mixer est compte par plant et bay. Ces deux objets contiennent une liste de composants-machine qui rfrencent mixer. Les rsultats pourraient tre pires. Supposez quun ingnieur ajoute lobjet plant en tant que composant du composite bay, un appel de getMachineCount() entrerait dans une boucle innie.
Solution 5.5

Voici une implmentation raisonnable de MachineComposite.isTree():


protected boolean isTree(Set visited) { visited.add(this); Iterator i = components.iterator(); while (i.hasNext()) { MachineComponent c = (MachineComponent) i.next(); if (visited.contains(c) || !c.isTree(visited)) return false; } return true; }

348

Partie VI

Annexes

Solution 5.6

Votre solution devrait montrer les liens de la Figure B.4.


Figure B.4
Les lignes paisses dans ce diagramme objet signalent le cycle inhrent au processus de fabrication.
buildInnerShell: ProcessStep

make: ProcessSequence

inspect: ProcessStep

reworkOrFinish: ProcessAlternation

:ProcessSequence

disassemble: ProcessStep

finish: ProcessStep

BRIDGE
Solution 6.1

Pour contrler diverses machines avec une interface commune, vous pouvez appliquer le pattern ADAPTER crant une classe dadaptation pour chaque contrleur. Chaque classe peut traduire les appels dinterface standard en appels grs par les contrleurs existants.
Solution 6.2

Votre code pourrait tre comme suit :


public void shutdown() { stopProcess(); conveyOut(); stopMachine(); }

Annexe B

Solutions

349

Solution 6.3

La Figure B.5 illustre une solution.


Figure B.5
Ce diagramme reprsente une abstraction une hirarchie de types de gestionnaires de machine distincte des implmentations de lobjet abstrait driver utilis par labstraction.
MachineManager2 driver:MachineDriver startMachine() shutdown() stopMachine() startProcess() stopProcess() conveyIn() HskMachineManager2 conveyOut() interface MachineDriver

setTimeout(:double)

FuserDriver

StarPressDriver

Solution 6.4

La Figure B.6 suggre une solution.


:Client createStatement() <<create>> :Statement executeQuery() <<create>> :ResultSet next() :Connection

Figure B.6
Ce diagramme illustre le ux de messages principal intervenant dans une application JDBC.

350

Partie VI

Annexes

Solution 6.5

Voici deux arguments en faveur de lcriture de code spcique SQL Server :


1. Nous ne pouvons prvoir le futur. Aussi, investir de largent maintenant en prvision dventualits qui pourraient ne jamais se produire est une erreur classique. Nous disposons de SQL Server maintenant, et davantage de vitesse signie de meilleurs temps de rponse, ce qui signie des fonds en banque aujourdhui. 2. En nous engageant exclusivement pour SQL Server, nous pouvons utiliser toutes les fonctionnalits offertes par la base de donnes, sans avoir nous inquiter de savoir si dautres drivers de base de donnes les supporteront.

Voici deux arguments en faveur de lemploi de drivers SQL gnriques :


1. Si nous utilisons des drivers SQL gnriques pour crire du code, il sera plus facile de le modier si nous changeons de fournisseur de base de donnes et commenons utiliser, par exemple, Oracle. En verrouillant le code pour SQL Server, nous limitons notre capacit tirer parti de loffre varie du march des bases de donnes. 2. Lemploi de drivers gnriques nous permet dcrire du code exprimental pouvant sexcuter avec des bases de donnes peu onreuses, telles que MySql, sans sappuyer sur un test spcique dans SQL Server.

Introduction la responsabilit
Solution 7.1

Voici quelques problmes que posent le diagramme :


m

La mthode Rocket.thrust() retourne un objet Rocket la place dun type quelconque de nombre ou de quantit physique. La classe LiquidRocket possde une mthode getLocation() bien que rien dans le diagramme ou le domaine de problmes ne suggre que les fuses doivent avoir un emplacement. Mme si nous lavions fait, il ny a aucune raison pour que les fuses combustible liquide pas les autres objets Rocket aient un emplacement. La mthode isLiquid() peut constituer une alternative acceptable lemploi de loprateur instanceof, mais nous nous attendrions alors aussi ce que la super-classe ait une mthode isLiquid() retournant false.

Annexe B

Solutions

351

CheapRockets (fuses bon march) est un nom pluriel bien que les noms de classes soient par convention au singulier. La classe CheapRockets implmente Runnable, bien que cette interface nait pas affaire avec les objets CheapRockets du domaine de problmes. Nous pourrions modliser laspect bon march avec seulement des attributs. Il ny a donc aucune justication la cration dune classe simplement pour des fuses bon march. La classe CheapRockets introduit une structuration du code qui entre en conit avec la structuration du modle de fuse, lequel peut tre combustible liquide ou solide. Par exemple, comment modliser une fuse combustible liquide bon march ? Le modle montre que Firework est une sous-classe de LiquidRocket impliquant que tous les artices sont des fuses combustible liquide, ce qui est faux. Le modle montre une relation directe entre les rservations et les types dartices, bien quaucune relation nexiste dans le domaine de problmes. La classe Reservation possde sa propre copie de city, quelle devrait obtenir par dlgation un objet Location. CheapRockets se compose dobjets Runnable, ce qui est tout simplement trange.

Solution 7.2

Lintrt de cet exercice est surtout de vous amener rchir sur ce qui constitue une bonne classe. Voyez si votre dnition considre les points suivants :
m

Voici une description basique dune classe : "Un ensemble nomm de champs contenant des valeurs de donnes, et de mthodes qui oprent sur ces valeurs" [Flanagan 2005, p. 71]. Une classe tablit un ensemble de champs, cest--dire les attributs dun objet. Le type de chaque attribut peut tre une classe, un type de donnes primitif, tel que boolean et int, ou une interface. Un concepteur de classes devrait tre en mesure dexpliquer de quelle faon les attributs dune classe sont lis.

352

Partie VI

Annexes

m m

Une classe devrait remplir un objectif logique. Le nom dune classe devrait reter la signication de la classe, la fois en tant que collection dattributs et en ce qui concerne son comportement. Une classe doit supporter tous les comportements quelle dnit, ainsi que ceux des super-classes et toutes les mthodes des interfaces quelle implmente la dcision de ne pas supporter une mthode dune super-classe ou dune interface peut occasionnellement se justier. La relation dune classe vis--vis de sa super-classe doit tre justiable. Le nom de chaque mthode dune classe devrait constituer un bon commentaire de ce quelle accomplit.

m m

Solution 7.3

Deux bonnes remarques sont que le rsultat dinvoquer une opration peut dpendre de ltat de lobjet rcepteur ou de la classe de lobjet rcepteur. A dautres moments, vous utilisez un nom impos par quelquun dautre. Un exemple de mthode dont leffet dpend de ltat de lobjet concern apparat au Chapitre 6, o la classe MachineManager2 possde une mthode stopMachine(). Le rsultat de lappel de cette mthode dpend du driver qui est en place pour lobjet MachineManager2. Lorsque le polymorphisme fait partie de la conception, leffet dinvoquer une opration peut partiellement ou totalement dpendre de la classe de lobjet rcepteur. Ce principe apparat dans de nombreux patterns, surtout avec FACTORY METHOD, STATE, STRATEGY, COMMAND et INTERPRETER. Par exemple, les classes de stratgie dune hirarchie peuvent toutes implmenter une mthode getRecommended(), en utilisant diffrentes stratgies pour recommander un artice particulier. Il est facile de comprendre que getRecommended() recommandera un artice ; mais sans connatre la classe de lobjet qui reoit lappel de la mthode, il est impossible de connatre la stratgie sous-jacente qui sera utilise. Une troisime situation se produit lorsquune autre personne dnit le nom. Supposez que votre mthode agisse en tant que callback : vous avez redni la mthode mouseDown() de la classe MouseListener. Vous devez utiliser mouseDown() comme nom de mthode, mme sil nindique rien sur lintention de votre mthode.

Annexe B

Solutions

353

Solution 7.4

Le code compile sans problme. Laccs est dni au niveau classe et non au niveau objet. Aussi un objet Firework peut-il accder aux variables et mthodes prives dun autre objet Firework, par exemple.

SINGLETON
Solution 8.1

Pour empcher dautres dveloppeurs dinstancier votre classe, crez un seul constructeur avec un accs private. Notez que si vous crez dautres constructeurs non privs, ou ne crez pas de constructeurs du tout, dautres objets seront en mesure dinstancier votre classe.
Solution 8.2

Voici deux raisons de recourir linitialisation tardive, ou paresseuse (lazy) :


1. Vous pourriez ne pas disposer de sufsamment dinformations pour instancier un singleton au moment voulu. Par exemple, un singleton Factory pourrait tre forc dattendre que les machines relles tablissent des canaux de communication entre elles. 2. Vous pourriez choisir dinitialiser tardivement un singleton qui requiert des ressources, telles quune connexion une base de donnes, surtout sil y a une possibilit que lapplication conteneur ne require pas le singleton durant une certaine session.
Solution 8.3

Votre solution devrait liminer toute possibilit de confusion pouvant se produire lorsque deux threads appellent la mthode recordWipMove() peu prs au mme moment :
public void recordWipMove() { synchronized (classLock) { wipMoves++; } }

Est-il possible quun thread puisse devenir actif au beau milieu dune opration dincrmentation ? Absolument : toutes les machines nont pas quune seule instruction pouvant incrmenter une variable, et mme celles qui sont dans ce cas ne peuvent pas garantir que le compilateur les utilisera dans toutes les situations. Cest une bonne stratgie que de restreindre laccs aux donnes dun singleton dans une application multithread. Voir Concurrent Programming in Java [Lea 2000] pour plus dinformations sur de telles applications.

354

Partie VI

Annexes

Solution 8.4 OurBiggestRocket:


Cette classe a un nom appropri. Vous devriez normalement modliser des proprits, telles que "biggest" par exemple, par des attributs et non par des noms de classes. Si un dveloppeur devait grer cette classe, ce serait peut-tre en tant que singleton. Cette classe prsente le mme problme que OurBiggestRocket. Cest une classe utilitaire, avec uniquement des mthodes statiques et aucune instance. Ce nest pas un singleton. Notez quelle possde toutefois un constructeur priv. Cest galement une classe utilitaire. Bien que lobjet System.out soit un objet PrintStream avec des responsabilits uniques, il ne constitue pas une instance unique de PrintStream, qui nest pas un singleton. Un PrintSpooler est associ une imprimante ou quelques imprimantes. Il est peu vraisemblable que ce soit un singleton. Chez Oozinoz, il y a plusieurs imprimantes et vous pouvez rechercher leurs adresses respectives par lintermdiaire du singleton PrinterManager.

topSalesAssociate: Math:

System: PrintStream:

PrintSpooler: PrinterManager:

OBSERVER
Solution 9.1

Une solution possible est :


public JSlider slider() { if (slider == null) { slider = new JSlider(); sliderMax = slider.getMaximum(); sliderMin = slider.getMinimum(); slider.addChangeListener(this); slider.setValue(slider.getMinimum()); } return slider; } public void stateChanged(ChangeEvent e) { double val = slider.getValue();

Annexe B

Solutions

355

double tp = (val - sliderMin) / (sliderMax - sliderMin); burnPanel().setTPeak(tp); thrustPanel().setTPeak(tp); valueLabel().setText(Format.formatToNPlaces(tp, 2)); }

Ce code suppose quune classe auxiliaire Format existe pour formater le champ de valeur. Vous pourriez avoir utilis la place une expression telle que ""+tp ou Double.toString(tp) lemploi dun nombre de chiffres constant produit une animation plus uide.
Solution 9.2

Une solution est montre la Figure B.7. Pour permettre un champ de valeur de senregistrer pour tre noti des vnements de curseur, la conception dans la Figure B.7 cre une sous-classe JLabel qui implmente ChangeListener.
Figure B.7
Dans cette conception, les composants qui dpendent du curseur implmentent ChangeListener pour pouvoir senregistrer et tre notis des vnements de dplacement du curseur.
JPanel ChangeListener

2 ShowBallistics2 BallisticsPanel2

burnPanel(): BallisticsPanel2 slider():JSlider thrustPanel(): BallisticsPanel2 valueLabel(): BallisticsLabel2

stateChanged( e:ChangeEvent)

JLabel

ChangeListener

BallisticsLabel2

stateChanged( e:ChangeEvent)

La nouvelle conception permet aux composants dpendants du curseur de senregistrer pour tre notis et de sactualiser ainsi eux-mmes. Cest une amlioration discutable, mais nous restructurerons nouveau la conception vers une architecture Modle-Vue-Contrleur.

356

Partie VI

Annexes

Solution 9.3

La Figure B.8 illustre une solution.


Figure B.8
Cette conception prvoit que lapplication surveille le curseur. Le champ de valeur et les panneaux dafchage restent attentifs aux changements dun objet contenant la valeur tPeak.
JPanel ChangeListener

2 ShowBallistics2 BallisticsPanel2 stateChanged( e:ChangeEvent)

burnPanel(): BallisticsPanel2 slider():JSlider thrustPanel(): BallisticsPanel2 valueLabel(): BallisticsLabel2

JLabel

ChangeListener

BallisticsLabel2 stateChanged( e:ChangeEvent)

Un objet Tpeak qui contient une valeur de temps crte joue un rle central dans cette conception. Lapplication ShowBallistics3 cre lobjet Tpeak, et le curseur lactualise lorsque sa position change. Les composants dafchage (le champ de valeur et les panneaux de trac) restent " lcoute" de lobjet Tpeak en senregistrant en tant quobservateurs de lobjet.
Solution 9.4

Voici une solution possible :


package app.observer.ballistics3; import javax.swing.*; import java.util.*; public class BallisticsLabel extends JLabel implements Observer { public BallisticsLabel(Tpeak tPeak) { tPeak.addObserver(this); } public void update(Observable o, Object arg) { setText("" + ((Tpeak) o).getValue()); repaint(); } }

Annexe B

Solutions

357

Solution 9.5

Voici une solution possible :


protected JSlider slider() { if (slider == null) { slider = new JSlider(); sliderMax = slider.getMaximum(); sliderMin = slider.getMinimum(); slider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { if (sliderMax == sliderMin) return; tPeak.setValue( (slider.getValue() - sliderMin) / (sliderMax - sliderMin)); } }); slider.setValue(slider.getMinimum()); } return slider; }

Solution 9.6

La Figure B.9 illustre le ux dappels qui se produit lorsquun utilisateur dplace le curseur de lapplication de calculs balistiques.
:JSlider

:ChangeListener Couche GUI Couche mtier :Tpeak stateChanged() setValue()

:BallisticsLabel

update()

Figure B.9
La conception MVC force un cheminement des messages par une couche mtier.

358

Partie VI

Annexes

Solution 9.7

Votre diagramme devrait ressembler lexemple de la Figure B.10. Notez que vous pouvez appliquer la mme conception avec Observer et Observable. La cl de cette conception rside dans le fait que la classe Tpeak intressante se rend ellemme observable en grant un objet avec des capacits dcoute.
PropertyChangeListener PropertyChangeListener

BallisticsPanel

BallisticsLabel

Tpeak

PropertyChangeSupport

getValue() setValue(:double) addPropertyChangeListener( :PropertyChangeListener) firePropertyChange( propertyName:String, oldValue:Object, newValue:Object) removePropertyChangeListener( :PropertyChangeListener)

Figure B.10
La classe Tpeak peut ajouter des fonctionnalits dcoute en dlguant les appels orients coute un objet PropertyChangeSupport.

Annexe B

Solutions

359

MEDIATOR
Solution 10.1

La Figure B.11 illustre une solution.

MoveATub2

MoveATubMediator

MoveATub() assignButton():Button boxList():JList machineList():JList tubList():JList main(:String[])

MoveATubMediator( gui:MoveATub,data:NameBase) actionPerformed(:ActionEvent) valueChange( :ListSelectionEvent) updateTubList(:String)

ActionListener

ListSelectionListener

NameBase

boxes():Object[] tubNames():Object[] getMachineContaining(tubName:String):String put(tubName:String,toMachineName:String)

Figure B.11
La classe MoveATub gre la construction de composants et la classe MoveATubMediator gre les vnements.

Dans cette conception, la classe de mdiation rvle ce qui est cit dans Fowler et al. [1999] comme tant un symptme de "Feature Envy", o elle semble plus intresse par la classe de GUI que par elle-mme, comme le montre la mthode valueChanged():
public void valueChanged(ListSelectionEvent e) { //

360

Partie VI

Annexes

gui.assignButton().setEnabled( ! gui.tubList().isSelectionEmpty() && ! gui.machineList().isSelectionEmpty()); }

Le rejet par certains dveloppeurs de cet aspect "dsir de fonctionnalit" les carte de ce genre de conception. Vous pourrez toutefois prouver le besoin davoir une classe pour la construction et le placement dun composant de GUI et une classe spare pour grer linteraction avec le composant.
Solution 10.2

La Figure B.12 illustre une solution possible.


Figure B.12
Ce diagramme illustre le rle central du mdiateur.
assignButton data

mediator

tubList

actionPerformed() getMachineContaining() put()

updateTubList()

setEnabled(false)

La solution fournie met en valeur le rle du mdiateur en tant que dispatcher, recevant un vnement et assumant la responsabilit dactualiser tous les objets concerns.

Annexe B

Solutions

361

Solution 10.3

La Figure B.13 prsente un diagramme objet actualis.


Figure B.13
Deux machines pensent quelles contiennent un bac T308. Le modle objet accepte une situation que ni une table relationnelle ni la ralit naccepteraient les traits pais soulignent la situation problmatique.
ShowRocketClient :RocketImpl

:RocketImpl_Stub

getApogee() getApogee()

Le problme que le code du dveloppeur introduit est que la machine StarPress2402 pense toujours quelle possde un bac T308. Dans une table relationnelle, le changement des attributs de machine dune ligne retire automatiquement le bac de la machine prcdente. Cette suppression automatique ne se produit pas lorsque la relation est disperse travers un modle objet rparti. La modlisation correcte de la relation bac/machine ncessite une logique spciale que vous pouvez dplacer vers un objet mdiateur distinct.
Solution 10.4

Le code complet pour la classe TubMediator devrait ressembler lexemple suivant :


package com.oozinoz.machine; import java.util.*; public class TubMediator { protected Map tubToMachine = new HashMap(); public Machine getMachine(Tub t) { return (Machine) tubToMachine.get(t); } public Set getTubs(Machine m) { Set set = new HashSet();

362

Partie VI

Annexes

Iterator i = tubToMachine.entrySet().iterator(); while (i.hasNext()) { Map.Entry e = (Map.Entry) i.next(); if (e.getValue().equals(m)) set.add(e.getKey()); } return set; } public void set(Tub t, Machine m) { tubToMachine.put(t, m); } }

Solution 10.5
m m m

Le pattern FACADE peut aider la restructuration dune grande application. Le pattern BRIDGE peut placer les oprations abstraites dans une interface. Le pattern OBSERVER peut intervenir pour restructurer du code pour supporter une architecture MVC. Le pattern FLYWEIGHT extrait la partie immuable dun objet pour quelle puisse tre partage. Le pattern BUILDER place la logique de construction dun objet en dehors de la classe instancier. Le pattern FACTORYMETHOD permet de rduire les responsabilits dune hirarchie de classes en plaant un aspect des comportements dans une hirarchie parallle. Les patterns STATE et STRATEGY permettent de placer les comportements spciques un tat ou une stratgie dans des classes distinctes.

PROXY
Solution 11.1

Voici une solution possible :


public int getIconHeight() { return current.getIconHeight(); } public int getIconWidth() { return current.getIconWidth(); }

Annexe B

Solutions

363

public synchronized void paintIcon( Component c, Graphics g, int x, int y) { current.paintIcon(c, g, x, y); }

Solution 11.2

Voici quelques-uns des problmes que prsente cette conception :


m

La transmission dun sous-ensemble dappels seulement un objet ImageIcon sous-jacent est prilleuse. La classe ImageIconProxy hrite dune dizaine de champs et dau moins 25 mthodes de la classe ImageIcon. Pour tre un vritable proxy, lobjet ImageIconProxy devrait transmettre la totalit ou la plupart des appels. Une retransmission dtaille ncessiterait beaucoup de mthodes potentiellement fautives, et ce code demanderait de la maintenance supplmentaire mesure que la classe ImageIcon et ses super-classes changeraient dans le temps. Vous pouvez vous demander si limage "absente" et limage souhaite se trouvent au bon endroit dans la conception. Il pourrait tre plus logique que les images soient passes plutt que de rendre la classe responsable de leur obtention.

Solution 11.3

La mthode load() dnit comme image "Loading", alors que la mthode run(), qui sexcute dans un thread distinct, charge limage dsire :
public void load(JFrame callbackFrame) { this.callbackFrame = callbackFrame; setImage(LOADING.getImage()); callbackFrame.repaint(); new Thread(this).start(); } public void run() { setImage(new ImageIcon( ClassLoader.getSystemResource(filename)) .getImage()); callbackFrame.pack(); }

Solution 11.4

Comme le montre le diagramme de classes, un constructeur RocketImpl accepte une valeur de prix et dapoge.
Rocket biggie = new rocketImpl(29.95, 820);

364

Partie VI

Annexes

Vous pourriez dclarer biggie de type RocketImpl. Toutefois, limportant propos de biggie est quil remplisse lobjectif de linterface Rocket quun client rechercherait.
Solution 11.5

Le diagramme complt devrait ressembler lillustration de la Figure B.14. Une autre dcision lgitime, toutefois moins explicite, pourrait dclarer les deux rcepteurs de getApogee() comme tant de type Rocket. En fait, les programmes serveur et client se rfrent tous deux ces objets en tant quinstances de linterface Rocket.
Figure B.14
Un proxy transmet les appels dun client de sorte que lobjet distant apparaisse local au client.
ShowRocketClient :RocketImpl

:RocketImpl_Stub

getApogee() getApogee()

CHAIN OF RESPONSABILITY
Solution 12.1

Voici quelques-uns des inconvnients possibles de la conception CHAIN OF RESPONSABILITY utilise par Oozinoz pour trouver lingnieur responsable dune machine :
m

Nous navons pas spci comment la chane sera mise en place pour que les machines connaissent leur parent. Dans la pratique, il peut se rvler difcile de garantir que les parents ne soient jamais null. Il est concevable que le processus de recherche dun parent entre dans une boucle innie, selon la faon dont les parents sont implments.

Annexe B

Solutions

365

Tous les objets noffrent pas tous les comportements impliqus par ces nouvelles mthodes. Par exemple, llment du niveau suprieur na pas de parent. La conception prsente est insufsante quant aux dtails affrents la faon dont le systme sait quels ingnieurs sont actuellement prsents dans lusine et disponibles. On ne sait pas clairement quelle devrait tre, en temps rel, la porte de cette responsabilit.

Solution 12.2

Votre diagramme devrait ressembler lexemple prsent dans la Figure B.15.


Figure B.15
Chaque objet VisualizationItem peut indiquer son ingnieur responsable. En interne, un tel objet peut transmettre la requte un autre objet parent.
interface VisualizationItem

getResponsible():Engineer

MachineComponent

getResponsible():Engineer

Tool

getResponsible():Engineer

ToolCart

getResponsible():Engineer

366

Partie VI

Annexes

Avec cette conception, nimporte quel client de nimporte quel lment simul peut simplement demander llment son ingnieur responsable. Cette approche libre le client de la tche de dtermination des objets capables de comprendre la responsabilit et place cette charge au niveau des objets qui implmentent linterface VisualizationItem.
Solution 12.3

A. Un objet MachineComponent peut avoir un responsable assign. Si ce nest pas le cas, il passe la requte son parent :
public Engineer getResponsible() { if (responsible != null) return responsible; if (parent != null) return parent.getResponsible(); return null; }

B. Le code de Tool.Responsible rete la rgle stipulant que "les outils sont toujours assigns aux chariots doutils" :
public Engineer getResponsible() { return toolCart.getResponsible(); }

C. Le code de ToolCart rete la rgle stipulant que "les chariots doutils ont un ingnieur responsable" :
public Engineer getResponsible() { return responsible; }

Solution 12.4

Votre solution devrait ressembler au diagramme de la Figure B.16. Vos constructeurs devraient autoriser les objets Machine et MachineComposite tre instancis avec ou sans responsable assign. Chaque fois quun objet MachineComponent ne possde pas dingnieur assign, il peut obtenir un ingnieur de son parent.

Annexe B

Solutions

367

Figure B.16
Les constructeurs dans la hirarchie
MachineComponent parent:MachineComponent responsible:Engineer MachineComponent( parent:MachineComponent) responsible:Engineer) MachineComponent( parent:MachineComponent)

MachineComponent
supportent les rgles stipulant quun objet MachineRoot doit avoir un ingnieur responsable et que chaque objet MachineComponent, except lobjet racine, doit avoir un parent.

Machine

MachineComposite

Machine( parent:MachineComponent) Machine( parent:MachineComponent, responsible:Engineer)

MachineComposite( parent:MachineComponent) MachineComposite( parent:MachineComponent, responsible:Engineer)

MachineRoot

MachineRoot( responsible:Engineer)

Solution 12.5

Le pattern CHAIN OF RESPONSABILITY pourrait sappliquer aux objets non composites dans les situations suivantes :
m

Une chane dingnieurs de permanence suivent une rotation standard. Si lingnieur de permanence principal ne rpond pas un appel par pageur du service de

368

Partie VI

Annexes

production dans un certain intervalle de temps, le systme de notication appelle le prochain ingnieur dans la chane.
m

Des utilisateurs saisissent des informations, telles que la date dun vnement, et une chane danalyseurs syntaxiques tentent tour de rle de dcoder le texte saisi.

FLYWEIGHT
Solution 13.1

Un argument en faveur de limmuabilit des chanes. Dans la pratique, les chanes sont frquemment partages entre plusieurs clients, ce qui constitue lessentiel des dfauts qui apparaissent lorsquun client a accidentellement un impact sur un autre. Par exemple, une mthode qui retourne le nom dun client sous forme dune chane conservera gnralement une rfrence au nom. Si le client convertit la chane en lettre majuscules pour lutiliser dans une table indexe par hachage, le nom de lobjet Customer changera galement, si ce nest pour limmuabilit des chanes. Dans Java, vous pouvez produire une version dune chane en majuscules, mais il faut que cela soit un nouvel objet, et non une version modie de la chane initiale. Limmuabilit des chanes permet de les partager en toute scurit. De plus, cela aide le systme viter certains risques pour la scurit. Un argument contre limmuabilit des chanes. Limmuabilit des chanes nous vite de commettre certaines erreurs mais le prix payer est lourd. Premirement, le dveloppeur est priv de la possibilit de modier une chane, indpendamment de la faon dont il pourrait justier ce besoin. Deuximement, lajout de certaines rgles spciales un langage le rend plus difcile apprendre et utiliser. Java est bien plus difcile apprendre que le langage Smalltalk, qui est aussi puissant. Finalement, aucun langage informatique ne peut mempcher de commettre des erreurs. Je prfre pouvoir apprendre un langage rapidement et avoir ainsi le temps dapprendre mettre en place et utiliser un environnement de test.

Annexe B

Solutions

369

Solution 13.2

Vous pouvez dplacer les aspects immuables de Substance nom, symbole et poids atomique inclus vers une classe Chemical, comme le montre la Figure B.17.

Substance2 grams:double chemical:Chemical getName():String getSymbol():String getAtomicWeight():double getGrams():double getMoles():double name:String

Chemical

symbol:String atomicWeight:double getName():String getSymbol():String getAtomicWeight():double

Figure B.17
Ce diagramme montre la portion immuable de la classe Substance place dans une classe Chemical.

La classe Substance2 conserve maintenant une rfrence un objet Chemical. Par consquent, la classe Substance2 peut toujours offrir les mmes mthodes accesseurs que la classe Substance originale. En interne, ces accesseurs sappuient sur la classe Chemical, comme le montrent les mthodes de Substance2 suivantes :
public double getAtomicWeight() { return chemical.getAtomicWeight(); } public double getGrams() { return grams; } public double getMoles() { return grams / getAtomicWeight(); }

370

Partie VI

Annexes

Solution 13.3

Un moyen qui ne fonctionnera pas est de dclarer private le constructeur de Chemical. Cela empcherait la classe ChemicalFactory dinstancier la classe Chemical. Pour contribuer empcher les dveloppeurs dinstancier eux-mmes la classe Chemical, vous pouvez placer les classes Chemical et ChemicalFactory dans le mme package et offrir un accs par dfaut ("package") au constructeur de la classe Chemical.
Solution 13.4

Lapproche par classe imbrique est bien plus complexe mais plus complte pour sassurer que seule la classe ChemicalFactory2 peut instancier de nouveaux objets yweight. Le code rsultant devrait ressembler lexemple suivant :
package com.oozinoz.chemical2; import java.util.*; public class ChemicalFactory2 { private static Map chemicals = new HashMap(); class ChemicalImpl implements Chemical { private String name; private String symbol; private double atomicWeight; ChemicalImpl( String name, String symbol, double atomicWeight) { this.name = name; this.symbol = symbol; this.atomicWeight = atomicWeight; } public String getName() { return name; } public String getSymbol() { return symbol; } public double getAtomicWeight() { return atomicWeight; } public String toString() { return name + "(" + symbol + ")[" + atomicWeight + "]"; } } static { ChemicalFactory2 factory = new ChemicalFactory2();

Annexe B

Solutions

371

chemicals.put("carbon", factory.new ChemicalImpl("Carbon", "C", 12)); chemicals.put("sulfur", factory.new ChemicalImpl("Sulfur", "S", 32)); chemicals.put("saltpeter", factory.new ChemicalImpl( "Saltpeter", "KN03", 101)); //... } public static Chemical getChemical(String name) { return (Chemical) chemicals.get( name.toLowerCase()); } }

Ce code rpond aux trois exercices de la faon suivante :


1. La classe ChemicalImpl imbrique devrait tre prive pour que seule la classe ChemicalFactory2 puisse lutiliser. Notez que laccs la classe imbrique devrait tre de niveau package ou public pour que la classe conteneur puisse linstancier. Mme si vous dclariez le constructeur public, aucune autre classe ne pourrait utiliser le constructeur si la classe imbrique elle-mme est dclare private. 2. Le constructeur de ChemicalFactory2 utilise un initialisateur statique pour garantir que la classe ne construira quune seule fois la liste de substances chimiques. 3. La mthode getChemical() devrait rechercher les substances chimiques par nom dans la table indexe par hachage. Lexemple de code stocke et recherche les substances en utilisant la version en minuscules de leur nom.

Introduction la construction
Solution 14.1

Voici quelques-unes des rgles spciales concernant les constructeurs :


m

Si vous ne fournissez pas de constructeur pour une classe, Java en fournira un par dfaut. Les noms de constructeurs doivent correspondre aux noms de leurs classes respectives pour cette raison, ils commencent gnralement par une capitale, la diffrence de la plupart des noms de mthodes. Les constructeurs peuvent invoquer dautres constructeurs avec this() et super() tant que cette invocation reprsente la premire instruction dans le constructeur.

372

Partie VI

Annexes

Le "rsultat" de laction dun constructeur est une instance de la classe, alors que le type de retour dune mthode ordinaire peut tre nimporte quoi. Vous pouvez utiliser new ou la rexion pour invoquer un constructeur.

Solution 14.2

Le code suivant chouera la compilation une fois plac dans Fuse.java et QuickFuse.java.
package app.construction; public class Fuse { private String name; public Fuse(String name) { this.name = name; } }

et
package app.construction; public class QuickFuse extends Fuse { }

Le compilateur gnrera un message derreur dont la teneur sera peu prs la suivante :
Constructeur Fuse() super implicite indfini pour un constructeur par dfaut. Constructeur explicite obligatoire.

Cette erreur se produit lorsque le compilateur rencontre la classe QuickFuse et fournit un constructeur par dfaut. Celui-ci nattend pas dargument et invoque, nouveau par dfaut, le constructeur de la super-classe sans argument. Toutefois, la prsence dun constructeur Fuse() qui accepte un paramtre String signie que le compilateur ne fournira plus de constructeur par dfaut pour Fuse. Le constructeur par dfaut de QuickFuse ne peut alors plus invoquer le constructeur de la superclasse sans argument car il nexiste plus.
Solution 14.3

Le programme afche :
java.awt.Point[x=3,y=4]

Le programme a russi trouver un constructeur acceptant deux arguments et a cr un nouveau point avec les arguments donns.

Annexe B

Solutions

373

BUILDER
Solution 15.1

Une faon de rendre lanalyseur plus exible est de lui permettre daccepter plusieurs espaces aprs une virgule. Pour cela, changez la construction de lappel de split() de la manire suivante :
s.split(", *");

Ou bien, au lieu daccepter des espaces aprs une virgule, vous pouvez autoriser nimporte quel type despace en initialisant lobjet Regex comme suit :
s.split(",\\s*")

La combinaison de caractres \s signie la "catgorie" de caractres blancs dans les expressions rgulires. Notez que ces solutions supposent quil ny aura pas de virgule insre dans les champs. Plutt que de rendre lanalyseur plus souple, vous pouvez remettre en question lapproche. En particulier, vous pouvez solliciter des agences de voyages quelles commencent envoyer les rservations au format XML. Vous pouvez tablir un ensemble de balises de marquage utiliser et les lire au moyen dun analyseur XML.
Solution 15.2

La mthode build() de UnforgivingBuilder gnre une exception si un attribut est invalide et retourne sinon un objet Reservation valide. Voici une implmentation :
public Reservation build() throws BuilderException { if (date == null) throw new BuilderException("Valid date not found"); if (city == null) throw new BuilderException("Valid city not found"); if (headcount < MINHEAD) throw new BuilderException( "Minimum headcount is " + MINHEAD); if (dollarsPerHead.times(headcount) .isLessThan(MINTOTAL)) throw new BuilderException( "Minimum total cost is " + MINTOTAL); return new Reservation( date, headcount, city, dollarsPerHead, hasSite); }

374

Partie VI

Annexes

Le code vrie que les valeurs de date et de ville sont dnies et que les valeurs de nombre de personnes et de prix par tte sont acceptables. La super-classe ReservationBuilder dnit les constantes MINHEAD et MINTOTAL. Si le constructeur ne rencontre pas de problmes, il retourne un objet Reservation valide.
Solution 15.3

Comme vu prcdemment, le code doit gnrer une exception si la rservation omet de spcier une ville ou une date car il ny a aucun moyen de deviner ces valeurs. Pour ce qui est de lomission des valeurs de nombre de personne et de prix par tte, notez les points suivants :
m

Si la requte de rservation ne spcie ni le nombre de personnes ni le prix par tte, dnissez la quantit de personnes la valeur minimale, et assignez au prix par tte le rsultat de la division du prix total acceptable par la quantit minimale de personnes. Si le nombre de personnes est omis mais pas le prix par tte, dnissez la quantit de personnes la valeur minimale tout en veillant ce quelle soit sufsante pour atteindre la recette totale minimale acceptable pour un spectacle. Si le nombre de personnes est indiqu mais pas le prix par tte, dnissez celuici sufsamment haut pour gnrer la recette minimale.

Solution 15.4

Voici une solution possible :


public Reservation build() throws BuilderException { boolean noHeadcount = (headcount == 0); boolean noDollarsPerHead = (dollarsPerHead.isZero()); if (noHeadcount && noDollarsPerHead) { headcount = MINHEAD; dollarsPerHead = sufficientDollars(headcount); } else if (noHeadcount) { headcount = (int) Math.ceil( MINTOTAL.dividedBy(dollarsPerHead)); headcount = Math.max(headcount, MINHEAD); } else if (noDollarsPerHead) { dollarsPerHead = sufficientDollars(headcount); }

Annexe B

Solutions

375

check(); return new Reservation( date, headcount, city, dollarsPerHead, hasSite); }

Ce code sappuie sur une mthode check() qui est similaire la mthode build() de la classe InforgivingBuilder:
protected void check() throws BuilderException { if (date == null) throw new BuilderException("Valid date not found"); if (city == null) throw new BuilderException("Valid city not found"); if (headcount < MINHEAD) throw new BuilderException( "Minimum headcount is " + MINHEAD); if (dollarsPerHead.times(headcount) .isLessThan(MINTOTAL)) throw new BuilderException( "Minimum total cost is " + MINTOTAL); }

FACTORY METHOD
Solution 16.1

Une bonne rponse serait peut-tre de dire que vous navez pas besoin didentier la classe sous-jacente lobjet retourn par une mthode iterator(). Ce qui est important, cest que vous connaissiez linterface supporte par litrateur, ce qui vous permet dnumrer les lments dune collection. Toutefois, si vous devez connatre la classe, vous pouvez afcher son nom avec une ligne dinstruction du genre :
System.out.println(iter.getClass().getName());

Cette instruction afche :


java.util.AbstractList$Itr

La classe Itr est une classe interne de AbstractList. Vous ne devriez probablement jamais voir cette classe en travaillant avec Java.

376

Partie VI

Annexes

Solution 16.2

Il y a de nombreuses rponses possibles, mais toString() est probablement la mthode la plus frquemment utilise pour crer un nouvel objet. Par exemple, le code suivant cre un nouvel objet String:
String s = new Date().toString();

La cration de chanes se produit souvent en coulisses. Considrez la ligne suivante :


System.out.println(new Date());

Ce code cre un objet String partir de lobjet Date par lintermdiaire de la mthode toString() de lobjet Date. Une autre mthode couramment utilise pour crer un nouvel objet est clone(), une mthode qui retourne gnralement une copie de lobjet rcepteur.
Solution 16.3

Lobjectif du pattern FACTORY METHOD est de permettre un fournisseur dobjets de dterminer quelle classe instancier lors de la cration dun objet. Par comparaison, les clients de BorderFactory savent exactement quels types dobjets ils obtiennent. Le pattern appliqu avec BorderFactory est FLYWEIGHT dans ce sens que BorderFactory emploie le partage pour supporter efcacement un grand nombre de bordures. La classe BorderFactory dispense les clients de devoir grer la rutilisabilit des objets alors que FACTORY METHOD vite que les clients aient savoir quelle classe instancier.
Solution 16.4

La Figure B.18 montre que les deux classes de vrication de limite de crdit implmentent linterface CreditCheck. La classe de factory fournit une mthode qui retourne un objet CreditCheck. Le client qui appelle createCreditCheck() ne sait pas prcisment quelle est la classe des objets quil obtient. La mthode createCreditCheck() est statique. Aussi les clients nont-ils pas besoin dinstancier la classe CreditCheckFactory pour obtenir un objet CreditCheck. Vous pouvez dnir cette classe abstraite ou prvoir un constructeur priv si vous voulez empcher activement dautres dveloppeurs de linstancier.

Annexe B

Solutions

377

Figure B.18
Deux classes implmentent linterface CreditCheck. Le choix de la classe instancier dpend du fournisseur de services plutt que du client concern par la vrication de crdit.
CreditCheckFactory

createCreditCheck():CreditCheck

interface CreditCheck

creditLimit(id:int):double

CreditCheckOnline

creditLimit(id:int):double

CreditCheckOffline

creditLimit(id:int):double

Solution 16.5

En admettant que la mthode isAgencyUp() rete prcisment la ralit, le code pour createCreditCheck() est simple :
public static CreditCheck createCreditCheck() { if (isAgencyUp()) return new CreditCheckOnline(); return new CreditCheckOffline(); }

378

Partie VI

Annexes

Solution 16.6

La Figure B.19 illustre un diagramme acceptable pour la hirarchie parallle Machine/MachinePlanner.


Figure B.19
La logique de planication se trouve maintenant dans une hirarchie distincte. Chaque sous-classe de Machine sait quel planicateur instancier en rponse un appel de createPlanner().
Machine MachinePlanner createPlanner(): MachinePlanner #machine:Machine MachinePlanner( m:Machine) getAvailable():Date Fuser

Mixer

BasicPlanner

ShellAssembler

ShellPlanner

StarPress

StarPressPlanner

Ce diagramme montre que les sous-classes de MachinePlanner doivent implmenter la mthode getAvailable(). Il indique aussi que les classes de la hirarchie MachinePlanner acceptent un objet Machine dans leur constructeur. Cela permet au planicateur dinterroger lobjet bnciaire de la planication, relativement des critres tels que lemplacement de la machine et la quantit de produits quelle traite actuellement.
Solution 16.7

Une mthode createPlanner() pour la classe Machine pourrait ressembler lexemple suivant :
public MachinePlanner createPlanner() { return new BasicPlanner(this); }

Annexe B

Solutions

379

Les classes Fuser et Mixer peuvent compter sur le fait dhriter cette mthode, alors que les classes ShellAssembler et StarPress devront la rednir. Pour la classe StarPress, la mthode createPlanner() pourrait se prsenter comme suit :
public MachinePlanner createPlanner() { return new StarPressPlanner(this); }

Ces mthodes illustrent le pattern FACTORY METHOD. Lorsque nous avons besoin dun objet de planication, nous appelons la mthode createPlanner() de la machine concerne par la planication. Le planicateur spcique que nous obtenons dpend de la machine.

ABSTRACT FACTORY
Solution 17.1

Voici une solution possible :


public class BetaUI extends UI { public BetaUI () { Font oldFont = getFont(); font = new Font( oldFont.getName(), oldFont.getStyle() | Font.ITALIC, oldFont.getSize()); } public JButton createButtonOk() { JButton b = super.createButtonOk(); b.setIcon(getIcon("images/cherry-large.gif")); return b; } public JButton createButtonCancel() { JButton b = super.createButtonCancel(); b.setIcon(getIcon("images/cherry-large-down.gif")); return b; } }

Ce code adopte comme approche dutiliser autant que possible les mthodes de la classe de base.

380

Partie VI

Annexes

Solution 17.2

Une solution apportant une conception plus robuste serait de spcier les mthodes de cration attendues et les proprits standard de GUI dans une interface, comme illustr Figure B.20.
Figure B.20
Cette conception de classes de factory abstraites pour les composants de GUI rduit la dpendance des sous-classes vis--vis des modicateurs des mthodes de la classe UI.
interface UI NORMAL:UI createButton():Button createList( :Object[]):JList createPaddedPanel( c:Component):JPanel getFont():Font BetaUI

Solution 17.3

La Figure B.21 prsente une solution possible pour fournir dans Credit.Canada des classes concrtes qui implmentent les interfaces et les classes abstraites de Credit.
Figure B.21
Le package
com.oozinoz.credit.ca com.oozinoz.credit

com.oozinoz.credit.ca fournit
une famille de classes concrtes qui oprent une varit de vrications lors dun appel provenant du Canada.

CheckFactoryCanada

CreditCheckFactory

BillingCheckCanada

interface BillingCheck

ShippingCheckCanada

interface ShippingCheck

CreditCheckCanadaOnline

interface CreditCheck

CreditCheckOffline

Annexe B

Solutions

381

Une subtilit est que vous navez besoin que dune classe concrte pour une vrication de crdit hors ligne car, chez Oozinoz, ce contrle est le mme quelle que soit la provenance des appels.
Solution 17.4

Voici une solution possible :


package com.oozinoz.credit.ca; import com.oozinoz.check.*; public class CheckFactoryCanada extends CheckFactory { public BillingCheck createBillingCheck() { return new BillingCheckCanada(); } public CreditCheck createCreditCheck() { if (isAgencyUp()) return new CreditCheckCanadaOnline(); return new CreditCheckOffline(); } public ShippingCheck createShippingCheck() { return new ShippingCheckCanada(); } }

Votre solution devrait :


m

implmenter les mthodes create pour les mthodes hrites de la classe abstraite CreditCheckFactory; avoir linterface approprie pour le type de retour de chaque mthode create; retourner un objet CreditCheckOffline si lorganisme de crdit nest pas disponible.

m m

Solution 17.5

Voici une justication possible. Le placement de classes spciques un pays dans des packages distincts permet aux dveloppeurs de la socit Oozinoz dorganiser les applications et le travail de dveloppement. De plus, les packages propres chaque pays sont indpendants. Nous pouvons tre srs que, par exemple, les classes propres aux Etats-Unis nont aucun impact sur les classes spciques au Canada. Nous pouvons aussi facilement ajouter des fonctionnalits pour de nouveaux pays. Par exemple, si nous commenons travailler avec Mexico, nous pouvons crer un nouveau package qui fournira les services de contrle dont nous avons besoin dune manire cohrente pour ce pays.

382

Partie VI

Annexes

Cela apporte lavantage supplmentaire de nous permettre dassigner le package credit.mx un dveloppeur possdant une exprience de travail avec des services et des donnes du Mexique. Voici un argument contre. Bien que cette sparation soit valable en thorie, elle est excessive dans la pratique. Je prfrerais avoir un package avec toutes les classes, du moins jusqu ce nous ayons neuf ou dix pays grer. La rpartition de ces classes dans plusieurs packages multiplie par trois, si ce nest plus, le travail de gestion de la conguration lorsque je dois implmenter un changement qui couvre tous ces packages.

PROTOTYPE
Solution 18.1

Voici quelques avantages de cette conception :


m

Nous pouvons crer de nouveaux objets factory sans avoir crer une nouvelle classe. Nous pourrions mme crer un nouveau kit de GUI lors de lexcution. Nous pouvons produire un nouveau factory par copie en apportant de lgres modications. Par exemple, nous pouvons faire en sorte que le kit de GUI dune version soit identique au kit normal, except pour la police. Lapproche avec PROTOTYPE permet aux boutons et autres composants dun nouveau factory dhriter de valeurs, telles que des couleurs, dun prcdent factory.

Voici quelques inconvnients :


m

Lapproche avec PROTOTYPE permet de changer des valeurs, par exemple pour les couleurs et les polices, pour chaque factory, mais elle ne permet pas de produire de nouveaux kits ayant dautres comportements. La raison de stopper la prolifration des classes de kit de GUI nest pas claire. En quoi est-elle un problme ? Nous devons placer le code dinitialisation du kit quelque part, probablement dans des mthodes statiques de la classe UIKit propose. Cette approche ne diminue pas rellement la quantit de code grer.

Quelle est la rponse correcte ? Dans une telle situation, il peut tre utile dexprimenter. Ecrivez du code qui suive les deux conceptions et valuez leur comportement dans la pratique. Il y aura des situations o les membres dune quipe seront en dsaccord quant la direction suivre. Cest une bonne chose : cest le signe que vous mettez le doigt sur certains problmes et que vous discutez conception.

Annexe B

Solutions

383

Si vous ntes jamais en dsaccord, il est probable que vous ne soyez pas en train dlaborer la meilleure conception pour les cas o vous ntes pas daccord, mme aprs une analyse mrement rchie, vous pouvez recourir un architecte, un chef concepteur ou une tierce partie neutre pour prendre une dcision.
Solution 18.2

Une faon de rsumer la fonction de clone() est "nouvel objet, mmes champs". La mthode clone() cre un nouvel objet en utilisant la mme classe et les mmes types dattributs que pour lobjet original. Le nouvel objet reoit aussi les mmes valeurs de champs que loriginal. Si ces champs sont des types de base, tels que des entiers, les valeurs sont copies. Si toutefois ce sont des rfrences, ce sont les rfrences qui sont copies. Lobjet que la mthode clone() cre est une copie partielle (shallow copy), elle partage avec loriginal tout objet subordonn. Une copie complte (deep copy) inclurait des copies entires de tous les attributs de lobjet parent. Par exemple, si vous clonez un objet A qui pointe vers un objet B, une copie partielle crera un nouveau A qui pointe vers lobjet B original ; une copie complte crera un nouvel A pointant vers un nouveau B. Si vous voulez une copie complte, vous devrez implmenter votre propre mthode qui effectuera ce que vous souhaitez. Notez que, pour utiliser clone(), vous devez dclarer votre classe comme implmentant Cloneable. Cette interface de marquage ne possde aucune mthode mais sert dindicateur pour signaler que vous supportez clone() de manire intentionnelle.
Solution 18.3

Le code suggr ne gardera que trois objets, comme le montre la Figure B.22.
Figure B.22
Une conception insufsante pour le clonage peut crer une copie incomplte qui partage certains objets avec son prototype.
m1:Machine m2:Machine

:Location

384

Partie VI

Annexes

La version actuelle de la mthode clone() pour MachineSimulator appelle super.clone(), que la classe Object implmente. Cette mthode cre un nouvel objet avec les mmes champs. Des types primitifs, tels que les champs dinstance int dans MachineSimulator, sont copis. De plus, les rfrences dobjets, telles que le champ Location dans MachineSimulator, sont copies. Notez que cest la rfrence qui est copie, pas lobjet. Cela signie que Object.clone() cre la situation illustre Figure B.22. Supposez que vous changiez la trave et les coordonnes demplacement de la deuxime machine. Etant donn quil ny a quun objet Location, cette modication change lemplacement des deux machines simules !
Solution 18.4

Voici une solution possible :


public OzPanel copy2() { OzPanel result = new OzPanel(); result.setBackground(this.getBackground()); result.setForeground(this.getForeground()); result.setFont(this.getFont()); return result; }

Les deux mthodes copy() et copy2() exonrent les clients de OzPanel davoir invoquer un constructeur et supporter le concept de PROTOTYPE. Toutefois, lapproche manuelle de copy2() offre peut-tre une meilleure scurit. Elle sappuie sur le fait de savoir quels sont les attributs importants copier, mais vite davoir copier des attributs dont vous ignorez tout.

MEMENTO
Solution 19.1

Voici une implmentation possible de undo() pour FactoryModel:


public boolean canUndo() { return mementos.size() > 1; } public void undo() { if (!canUndo()) return; mementos.pop(); notifyListeners(); }

Annexe B

Solutions

385

Ce code veille ignorer les requtes undo() si la pile se retrouve dans son tat initial avec un seul mmento. Le sommet de la pile est toujours ltat courant, aussi le code undo() doit-il seulement effectuer un pop() pour exposer le mmento prcdent. Lorsque vous crivez une mthode createMemento(), vous devriez faire en sorte quelle retourne toutes les informations ncessaires pour reconstruire lobjet rcepteur. Dans cet exemple, un simulateur de machine peut se reconstruire partir dun clone, et un simulateur dusine peut se reconstruire partir dune liste de clones de simulateurs de machines.
Solution 19.2

Une solution possible est :


public void stateChanged(ChangeEvent e) { machinePanel().removeAll(); List locations = factoryModel.getLocations(); for (int i = 0; i < locations.size(); i++) { Point p = (Point) locations.get(i); machinePanel().add(createPictureBox(p)); } undoButton().setEnabled(factoryModel.canUndo()); repaint(); }

Chaque fois que ltat change, ce code reconstruit entirement la liste de machines dans machinePanel().
Solution 19.3

Stocker un mmento en tant quobjet suppose que lapplication soit toujours en train de sexcuter lorsque lutilisateur souhaite restaurer lobjet original. Voici les raisons qui pourraient vous inciter stocker un mmento sous forme persistante :
m m

pouvoir restaurer ltat dun objet si le systme plante ; permettre lutilisateur de quitter le systme et de poursuivre son travail ultrieurement ; pouvoir reconstruire un objet sur un autre ordinateur.

386

Partie VI

Annexes

Solution 19.4

Une solution possible est :


public void restore(Component source) throws Exception { JFileChooser dialog = new JFileChooser(); dialog.showOpenDialog(source); if (dialog.getSelectedFile() == null) return; FileInputStream out = null; ObjectInputStream s = null; try { out = new FileInputStream(dialog.getSelectedFile()); s = new ObjectInputStream(out); ArrayList list = (ArrayList) s.readObject(); factoryModel.setLocations(list); } finally { if (s != null) s.close(); } }

Ce code est presque identique la mthode save(), sauf que la mthode restore() doit demander au modle dusine de lui transmettre par pouss (push) la liste de machines rcupre.
Solution 19.5

Encapsuler limite laccs ltat et aux oprations dun objet. Le fait denregistrer un objet, tel quune collection demplacements, sous forme textuelle expose les donnes de lobjet et permet nimporte qui disposant dune diteur de texte de changer ltat de lobjet. Par consquent, enregistrer un objet au format XML viole la rgle dencapsulation, dans une certaine mesure en tout cas. Selon votre application, une violation de la rgle dencapsulation par un stockage persistant peut poser un problme dans la pratique. Pour y remdier, vous pourriez limiter laccs aux donnes, ce qui est courant dans une base de donnes relationnelle. Vous pourriez sinon chiffrer les donnes, ce qui est courant lors de la transmission de texte HTML sensible. Limportant ici nest pas de savoir si les concepts dencapsulation et de mmento sappliquent une conception mais plutt de garantir lintgrit des donnes tout en supportant le stockage et la transmission de donnes.

Annexe B

Solutions

387

Introduction aux oprations


Solution 20.1

Le pattern CHAIN OF RESPONSIBILITY distribue une opration travers une chane dobjets. Chaque mthode implmente le service de lopration directement ou transmet les appels lobjet suivant dans la chane.
Solution 20.2

Voici une liste complte des modicateurs de mthodes Java avec une dnition informelle de chacun :
m m

public : accs permis tous les clients. protected : accs permis au sein du package de dclaration et aux sous-classes de la classe. private : accs permis uniquement au sein de la classe. abstract : pas dimplmentation fournie. static : associe la classe dans son ensemble, pas des objets individuels. final : ne peut tre remplace. synchronized : la mthode acquiert un accs au moniteur de lobjet ou de la classe, si elle est statique. native : implmente ailleurs dans du code dpendant dune plate-forme. strictfp : les expressions double et float values strictement daprs les rgles FP, ncessitant que les rsultats intermdiaires soient valides selon les standards IEEE.

m m m m m

m m

Bien que certains dveloppeurs puissent tre tents dutiliser tous ces modicateurs dans une mme dnition de mthode, plusieurs rgles limitent les possibilits de combinaison.

388

Partie VI

Annexes

Solution 20.3

Cela dpend. Avec les versions antrieures Java 5, si vous changez la valeur de retour de Bitmap.clone(), le code ne sera pas compil. La signature de clone() correspond la signature de Object.clone(), et les types de retour doivent donc galement correspondre. Depuis Java 5, la dnition du langage a chang pour supporter des types de retour covariants, permettant une sous-classe de dclarer un type de retour plus spcique.
Solution 20.4

Voici un argument pour le fait domettre les dclarations dexceptions dans les enttes de mthodes. Tout dabord, il convient de noter que Java nimpose pas que les mthodes dclarent toutes les exceptions qui pourraient tre gnres. Nimporte quelle mthode pourrait, par exemple, rencontrer un pointeur null et gnrer une exception non dclare. Il ne serait donc pas pratique dobliger les programmeurs dclarer toutes les exceptions possibles. Les applications requirent une stratgie pour pouvoir grer toutes les exceptions, laquelle ne peut tre remplace en demandant aux dveloppeurs de dclarer certains types dexceptions. Un argument contre est que les programmeurs ont besoin daide. Il est vrai que larchitecture dune application doit disposer dune stratgie efcace de gestion des exceptions. De toute vidence, il ne serait pas commode de forcer les dveloppeurs dclarer dans chaque mthode lventualit dun problme courant, tel que des pointeurs null. Mais, pour certaines erreurs, telles quun problme douverture de chier, demander linvocateur dune mthode de grer les exceptions possibles pourrait tre utile. C# rsout ce dilemme en liminant toute dclaration dexception de len-tte des mthodes.
Solution 20.5

La gure illustre un algorithme la procdure dterminant si un modle objet est un arbre , deux oprations apparaissant en tant que signatures dans la classe MachineComponent, et quatre mthodes.

Annexe B

Solutions

389

TEMPLATE METHOD
Solution 21.1

Votre programme complt devrait ressembler ce qui suit :


package app.templateMethod; import java.util.Comparator; import com.oozinoz.firework.Rocket; public class ApogeeComparator implements Comparator { public int compare(Object o1, Object o2) { Rocket r1 = (Rocket) o1; Rocket r2 = (Rocket) o2; return Double.compare(r1.getApogee(), r2.getApogee()); } }

et
package app.templateMethod; import java.util.Comparator; import com.oozinoz.firework.Rocket; public class NameComparator implements Comparator { public int compare(Object o1, Object o2) { Rocket r1 = (Rocket) o1; Rocket r2 = (Rocket) o2; return r1.toString().compareTo(r2.toString()); } }

Solution 21.2

Le code de markMoldIncomplete() passe des informations relatives au moule incomplet au gestionnaire du matriel. Voici une solution possible :
package com.oozinoz.ozAster; import aster.*; import com.oozinoz.businessCore.*; public class OzAsterStarPress extends AsterStarPress { public MaterialManager getManager() { return MaterialManager.getManager(); } public void markMoldIncomplete(int id) { getManager().setMoldIncomplete(id); } }

390

Partie VI

Annexes

Solution 21.3

Vous avez besoin ici dun hook. Vous pourriez formuler votre demande comme ceci : "Vous serait-il possible dinsrer un appel dans votre mthode shutDown(), aprs le dchargement de la pte et avant le rinage de la machine ? Si vous le nommiez quelque chose comme collectPaste(), je pourrais lutiliser pour rcuprer la pte que nous rutilisons chez Oozinoz." Les dveloppeurs de chez Aster discuteraient probablement avec vous du nom donner la mthode. Lintrt de demander un hook dans un TEMPLATE METHOD est que cela permet votre code dtre beaucoup plus robuste que si vous deviez vous accommoder dun code existant inadquat.
Solution 21.4

La mthode getPlanner() de la classe Machine devrait tirer parti de la mthode abstraite createPlanner():
public MachinePlanner getPlanner() { if (planner == null) planner = createPlanner(); return planner; }

Ce code implique que vous ajoutiez un champ planner la classe Machine. Aprs avoir ajout cet attribut et la mthode getPlanner(), vous pouvez les supprimer dans les sous-classes. Cette refactorisation cre un TEMPLATE METHOD. La mthode getPlanner() procde une initialisation paresseuse de la variable planner, sappuyant uniquement sur ltape createPlanner() fournie par les sous-classes.

STATE
Solution 22.1

Comme le montre la machine tats, lorsque la porte est ouverte, le fait de toucher le bouton la place dans ltat StayOpen, et le fait de toucher le bouton une seconde fois la fait se fermer.
Solution 22.2

Votre code devrait ressembler ce qui suit :


public void complete() { if (state == OPENING)

Annexe B

Solutions

391

setState(OPEN); else if (state == CLOSING) setState(CLOSED); } public void timeout() { setState(CLOSING); }

Solution 22.3

Votre code devrait ressembler ce qui suit :


package com.oozinoz.carousel; public class DoorClosing extends DoorState { public DoorClosing(Door2 door) { super(door); } public void touch() { door.setState(door.OPENING); } public void complete() { door.setState(door.CLOSED); } }

Solution 22.4

La Figure B.23 illustre le diagramme complt.


Figure B.23
Cette conception rend les objets DoorState constants. Les mthodes de transition dtat de DoorState actualisent ltat dun objet Door quelles reoivent comme paramtre.
Door DoorState CLOSED:DoorClosed complete() setState( state:DoorState) timeout() touch() status() complete(d:Door) timeout(d:Door) touch(d:Door) status() OPENING:DoorOpening OPEN:DoorOpen STAYOPEN:DoorStayOpen CLOSING:DoorClosing

392

Partie VI

Annexes

STRATEGY
Solution 23.1

La Figure B.24 prsente une solution.


Customer BIG_SPENDER_DOLLARS:int getAdvisor():Advisor isRegistered():boolean isBigSpender():boolean getRecommended():Firework spendingSince(d:Date): double ItemAdvisor GroupAdvisor recommend(c:Customer):Firework interface Advisor

PromotionAdvisor

RandomAdvisor

Figure B.24
La politique publicitaire dOozinoz inclut quatre stratgies qui apparaissent sous la forme de quatre implmentations de linterface Advisor.

Solution 23.2

Les classes GroupAdvisor et ItemAdvisor sont des instances de ADAPTER, fournissant linterface quun client attend en utilisant les services dune classe dont linterface est diffrente.
Solution 23.3

Votre code pourrait ressembler ceci :


public Firework getRecommended() { return getAdvisor().recommend(this); }

Une fois que lattribut advisor est connu, le polymorphisme fait le reste.

Annexe B

Solutions

393

Solution 23.4

Une routine de tri rutilisable est-elle un exemple de TEMPLATE METHOD ou de STRATEGY? Daprs Design Patterns, TEMPLATE METHOD laisse les sous-classes rednir certaines tapes dun algorithme. Mais la mthode Collections.sort() ne sappuie pas sur les sous-classes. Elle utilise une instance de Comparator. Chaque instance de Comparator fournit une nouvelle mthode et donc un nouvel algorithme et une nouvelle stratgie. Cette mthode est donc un bon exemple de STRATEGY. Il existe de nombreux algorithmes de tri, mais Collections. sort() en utilise un seul, le tri rapide (quick sort). Changer dalgorithme signierait utiliser la place le tri par tas (heap sort) ou le tri bulles (bubble sort). Lobjectif de STRATEGY est de vous permettre dutiliser diffrents algorithmes, ce qui ne se produit pas ici. Lobjectif de TEMPLATE METHOD est de vous permettre dinsrer une tape dans un algorithme, ce qui correspond prcisment au fonctionnement de la mthode sort().

COMMAND
Solution 24.1

Nombre dapplications Java Swing appliquent le pattern MEDIATOR enregistrant un seul objet pour recevoir tous les vnements GUI. Cet objet sert de mdiateur aux composants qui interagissent et traduit lentre utilisateur en commandes dobjets du domaine.
Solution 24.2

Voici quoi pourrait ressembler votre code :


package com.oozinoz.visualization; import java.awt.event.*; import javax.swing.*; import com.oozinoz.ui.*; public class Visualization2 extends Visualization { public static void main(String[] args) { Visualization2 panel = new Visualization2(UI.NORMAL); JFrame frame = SwingFacade.launch( panel, "Operational Model"); frame.setJMenuBar(panel.menus()); frame.setVisible(true); }

394

Partie VI

Annexes

public Visualization2(UI ui) { super(ui); } public JMenuBar menus() { JMenuBar menuBar = new JMenuBar(); JMenu menu = new JMenu("File"); menuBar.add(menu); JMenuItem menuItem = new JMenuItem("Save As..."); menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { save(); } }); menu.add(menuItem); menuItem = new JMenuItem("Restore From..."); menuItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { restore(); } }); menu.add(menuItem); return menuBar; } public void save() { try { mediator.save(this); } catch (Exception ex) { System.out.println("Echec de sauvegarde : " + ex.getMessage()); } } public void restore() { try { mediator.restore(this); } catch (Exception ex) { System.out.println("Echec de restauration : " + ex.getMessage()); } } }

Bien que la mthode actionPerformed() ncessite un argument ActionEvent, vous pouvez lignorer sans problme. La mthode menus() enregistre une seule instance dune classe anonyme avec loption de menu Save et une seule instance dune autre classe anonyme avec loption de menu Load. Lorsque ces mthodes sont appeles, il ny a aucun doute possible quant la source de lvnement.

Annexe B

Solutions

395

Solution 24.3

La mthode testSleep() passe la commande doze la mthode time():


package app.command; import com.oozinoz.robotInterpreter.Command; import com.oozinoz.utility.CommandTimer; import junit.framework.TestCase; public class TestCommandTimer extends TestCase { public void testSleep() { Command doze = new Command() { public void execute() { try { Thread.sleep( 2000 + Math.round(10 * Math.random())); } catch (InterruptedException ignored) { } } }; long actual = CommandTimer.time(doze); long expected = 2000; long delta = 5; assertTrue( "Devrait tre " + expected + " +/- " + delta + " ms", expected - delta <= actual && actual <= expected + delta); } }

Solution 24.4

Votre code pourrait ressembler ceci :


public void shutDown() { if (inProcess()) { stopProcessing(); moldIncompleteHook.execute(this); } usherInputMolds(); dischargePaste(); flush(); }

Notez que ce code ne soccupe pas de vrier si moldIncompleteHook est null, puisque cet argument est toujours dni avec un vritable objet Hook (initialement, il est dni avec un objet NullHook qui ne fait rien, mais un utilisateur peut installer un hook diffrent).

396

Partie VI

Annexes

Vous pourriez lutiliser comme suit :


package app.templateMethod; import com.oozinoz.businessCore.*; import aster2.*; public class ShowHook { public static void main(String[] args) { AsterStarPress p = new AsterStarPress(); Hook h = new Hook() { public void execute(AsterStarPress p) { MaterialManager m = MaterialManager.getManager(); m.setMoldIncomplete(p.getCurrentMoldID()); } }; p.setMoldIncompleteHook(h); } }

Solution 24.5

Dans FACTORY METHOD, un client sait quand crer un nouvel objet mais ne sait pas quel type dobjet. Ce pattern place la cration dun objet dans une mthode permettant au client de ne pas connatre la classe instancier. Ce principe est galement prsent dans ABSTRACT FACTORY.
Solution 24.6

Lobjectif du pattern MEMENTO est dassurer le stockage et la restauration de ltat dun objet. Typiquement, vous pouvez ajouter un nouveau mmento une pile pour chaque excution dune commande, puis les rcuprer et les appliquer chaque fois quun utilisateur a besoin de dfaire des commandes.

INTERPRETER
Solution 25.1

La mthode execute() de la classe ForCommand devrait ressembler au code suivant :


private void execute(MachineComponent mc) { if (mc instanceof Machine) { Machine m = (Machine) mc; variable.assign(new Constant(m)); body.execute(); return; }

Annexe B

Solutions

397

MachineComposite comp = (MachineComposite) mc; List children = comp.getComponents(); for (int i = 0; i < children.size(); i++) { MachineComponent child = (MachineComponent) children.get(i); execute(child); } }

Le code de execute() parcourt un composite de machines. Lorsquil rencontre un nud feuille une machine , il assigne la variable la machine et excute la commande body de lobjet ForMachine.
Solution 25.2

Une solution possible est :


public void execute() { if (term.eval() != null) body.execute(); else elseBody.execute(); }

Solution 25.3

Voici une manire dcrire la classe WhileCommand.java:


package com.oozinoz.robotInterpreter2; public class WhileCommand extends Command { protected Term term; protected Command body; public WhileCommand(Term term, Command body) { this.term = term; this.body = body; } public void execute() { while (term.eval() != null) body.execute(); } }

Solution 25.4

Voici un exemple de rponse. Lobjectif du pattern INTERPRETER est de vous permettre de composer des objets excutables partir dune hirarchie de classes fournissant diffrentes interprtations dune opration commune. Lobjectif du pattern COMMAND est simplement dencapsuler une requte dans un objet.

398

Partie VI

Annexes

Un objet interprteur peut-il fonctionner comme une commande ? Bien entendu ! Le choix du pattern utiliser dpend de votre but. Etes-vous en train de crer un kit doutils pour composer des objets excutables ou dencapsuler une requte dans un objet ?

Introduction aux extensions


Solution 26.1

En mathmatique, un cercle est un cas spcial dellipse. Mais en programmation OO, une ellipse ne possde pas le mme comportement quun cercle. Par exemple, sa largeur peut faire le double de sa hauteur, ce qui est impossible pour un cercle. Si ce comportement est important pour votre programme, un objet Circle ne fonctionnera pas comme un objet Ellipse et constituera donc une violation de LSP. Notez quil peut en aller autrement pour les objets immuables. Cest simplement un domaine dans lequel les mathmatiques naves saccordent mal avec la smantique des hirarchies de types standard.
Solution 26.2

Lexpression tub.getLocation().isUp() peut donner lieu des erreurs de programmation si certaines subtilits entourent la valeur de la proprit Location dun objet Tub. Par exemple, cette proprit pourrait tre null ou tre un objet Robot si le bac est en transit. Dans le premier cas, lvaluation de tub.getLocation().isUp() gnre une exception ; dans le second, le problme peut mme tre pire puisque nous tentons dutiliser un robot pour rcuprer un bac partir de luimme. Ces problmes potentiels sont grables, mais voulons-nous quun tel code de gestion soit plac dans la mthode contenant lexpression tub.getLocation().isUp()? Non. Le code ncessaire se trouve peut-tre dj dans la classe Tub. Si ce nest pas le cas, il devrait en tout cas sy trouver pour nous viter davoir coder les mmes subtilits dans dautres mthodes qui interagissent avec les bacs.
Solution 26.3

Voici un exemple :
public static String getZip(String address) { return address.substring(address.length() - 5); }

Annexe B

Solutions

399

Cette mthode comporte quelques indicateurs, dont un indicateur obsession primitive (utilisation dune chane pour contenir plusieurs attributs).
Solution 26.4

Voici un exemple de solution :


Exemple Le concepteur dune simulation pyrotechnique tablit une interface qui dnit le comportement que doit prsenter votre objet pour pouvoir prendre part la simulation Un kit doutils permet de composer des objets excutables lors de lexcution Une super-classe possde une mthode qui dpend de sous-classes pour fournir une tape manquante Un objet vous permet dtendre son comportement en acceptant une mthode encapsule dans un autre objet et en invoquant cette mthode au moment appropri Un gnrateur de code insre un comportement qui donne lillusion quun objet sexcutant sur une autre machine est local Une conception vous permet denregistrer des listeners pour des rappels qui auront lieu lorsquun objet change Une conception vous permet de dnir des oprations abstraites qui dpendent dune interface spcique et dajouter de nouveaux drivers qui satisfont aux exigences de cette interface Pattern luvre

ADAPTER

INTERPRETER TEMPLATE METHOD COMMAND

PROXY OBSERVER BRIDGE

DECORATOR
Solution 27.1

Voici une solution possible :


package com.oozinoz.filter; import java.io.*; public class RandomCaseFilter extends OozinozFilter { public RandomCaseFilter(Writer out) { super(out); } public void write(int c) throws IOException {

400

Partie VI

Annexes

out.write(Math.random() < .5 ? Character.toLowerCase((char) c) : Character.toUpperCase((char) c)); } }

Une casse alatoire accroche lattention. Considrez le programme suivant :


package app.decorator; import java.io.BufferedWriter; import java.io.IOException; import com.oozinoz.filter.ConsoleWriter; import com.oozinoz.filter.RandomCaseFilter; public class ShowRandom { public static void main(String[] args) throws IOException { BufferedWriter w = new BufferedWriter( new RandomCaseFilter(new ConsoleWriter())); w.write("buy two packs now and get a " + "zippie pocket rocket -- free!"); w.newLine(); w.close(); } }

Ce programme produit quelque chose comme ceci :


bUy tWO pAcks NOw ANd geT A ZiPpIE PoCkEt RocKeT -- frEe!

Solution 27.2

Voici une solution possible :


package com.oozinoz.filter; import java.io.Writer; public class ConsoleWriter extends Writer { public void close() {} public void flush() {} public void write( char[] buffer, int offset, int length) { for (int i = 0; i < length; i++) System.out.print(buffer[offset + i]); } }

Annexe B

Solutions

401

Solution 27.3

Voici une solution possible :


package com.oozinoz.function; public class Exp extends Function { public Exp(Function f) { super(f); } public double f(double t) { return Math.exp(sources[0].f(t)); } }

Solution 27.4

Voici une solution possible :


package app.decorator.brightness; import com.oozinoz.function.Function; public class Brightness extends Function { public Brightness(Function f) { super(f); } public double f(double t) { return Math.exp(-4 * sources[0].f(t)) * Math.sin(Math.PI * sources[0].f(t)); } }

ITERATOR
Solution 28.1

La routine display() lance un nouveau thread qui peut se rveiller nimporte quel moment, mais lappel sleep() veille ce que run() sexcute pendant que display() est inactive. Le rsultat indique que lorsquelle sexcute, la mthode display() garde le contrle pendant une itration, afchant la liste partir de lindice 0 :
Mixer1201

Ensuite, le second thread devient actif et place Fuser1101 au dbut de la liste, dcalant toutes les autres machines dun indice. En particulier, Mixer1201 passe de lindice 0 lindice 1.

402

Partie VI

Annexes

Lorsque le thread principal reprend le contrle, display() afche le reste de la liste depuis lindice 1 jusqu la n :
Mixer1201 ShellAssembler1301 StarPress1401 UnloadBuffer1501

Solution 28.2

Un argument contre lutilisation des mthodes synchronized() est quelles peuvent donner lieu des rsultats errons si litration utilise une boucle for ou faire planter le programme, moins de prvoir une logique qui intercepte lexception InvalidOperationException gnre. Un argument contre lapproche avec verrouillage est que les conceptions qui assurent une itration avec scurit inter-thread sappuient sur la coopration entre les threads pouvant accder la collection. Les mthodes synchronized() servent justement dans le cas o les threads ne cooprent pas. Ni les mthodes synchronized() ni le support du verrouillage intgrs Java ne peuvent rendre le dveloppement multithread ais et infaillible.
Solution 28.3

Comme dcrit au Chapitre 16, les itrateurs constituent un exemple classique du pattern FACTORY METHOD. Un client qui souhaite un numrateur pour une instance de ProcessComponent sait quand crer litrateur, et la classe rceptrice sait quelle classe instancier.
Solution 28.4

Voici une solution possible :


public Object next() { if (peek != null) { Object result = peek; peek = null; return result; } if (!visited.contains(head)) { visited.add(head); if (shouldShowInterior()) return head; } return nextDescendant(); }

Annexe B

Solutions

403

VISITOR
Solution 29.1

La diffrence se situe au niveau du type de lobjet this. La mthode accept() invoque la mthode visit() dun objet MachineVisitor. La mthode accept() de la classe Machine recherche une mthode visit() possdant la signature visit(:Machine), tandis que la mthode accept() de la classe MachineComposite recherche une mthode visit() possdant la signature visit(:MachineComposite).
Solution 29.2

Voici une solution possible :


package app.visitor; import com.oozinoz.machine.MachineComponent; import com.oozinoz.machine.OozinozFactory; public class ShowFindVisitor { public static void main(String[] args) { MachineComponent factory = OozinozFactory.dublin(); MachineComponent machine = new FindVisitor().find( factory, 3404); System.out.println(machine != null ? machine.toString() : "Introuvable"); } }

Solution 29.3

Voici une solution possible :


package app.visitor; import com.oozinoz.machine.*; import java.util.*; public class RakeVisitor implements MachineVisitor { private Set leaves; public Set getLeaves(MachineComponent mc) { leaves = new HashSet(); mc.accept(this); return leaves; } public void visit(Machine m) { leaves.add(m); } public void visit(MachineComposite mc) {

404

Partie VI

Annexes

Iterator iter = mc.getComponents().iterator(); while (iter.hasNext()) ((MachineComponent) iter.next()).accept(this); } }

Solution 29.4

Une solution est dajouter un argument Set toutes les mthodes accept() et visit() pour passer lensemble des nuds visits. La classe ProcessComponent devrait alors inclure une mthode accept() concrte appelant sa mthode accept() abstraite, lui passant un nouvel objet Set:
public void accept(ProcessVisitor v) { accept(v, new HashSet()); }

La mthode accept() des sous-classes ProcessAlternation, ProcessSequence et ProcessStep serait :


public void accept(ProcessVisitor v, Set visited) { v.visit(this, visited); }

Les dveloppeurs doivent aussi crer des classes avec des mthodes visit() qui acceptent lensemble visited. Il leur revient galement de remplir lensemble.
Solution 29.5

Voici les alternatives lapplication du pattern VISITOR:


m

Ajoutez le comportement dont vous avez besoin la hirarchie originale. Vous pouvez le faire si vous communiquez facilement avec ses dveloppeurs ou si vous suivez le principe de proprit collective du code. Vous pouvez laisser une classe simplement traverser une structure de machines ou de processus sur laquelle elle doit oprer. Si vous avez besoin de connatre le type dun enfant du composite, par exemple, vous pouvez utiliser loprateur instanceof, ou vous pourriez sinon introduire des fonctions boolennes, telles que isLeaf() et isComposite(). Si le comportement que vous voulez ajouter est trs diffrent de celui existant, vous pouvez crer une hirarchie parallle. Par exemple, la classe MachinePlanner du Chapitre 16, consacr FACTORY METHOD, place un comportement de planication dans une hirarchie spare.

C
Code source dOozinoz
Le premier avantage de comprendre les patterns, ou modles, de conception est quils vous aident amliorer votre code. Vous obtiendrez un code plus concis, plus simple, plus lgant et plus puissant, et sa maintenance en sera aussi facilite. Pour en percevoir les rels bnces, voyez-les en action dans du code excut. Vous devez devenir laise dans la construction ou reconstruction dune base de code avec des patterns. Il peut tre utile de commencer avec des exemples fonctionnels, et ce livre inclut de nombreux exemples dimplmentation illustrant leur emploi dans du code Java. La compilation du code source dOozinoz et lexamen des exemples fournis tout au long du livre pour tayer les concepts dcrits vous aideront dbuter limplmentation de patterns dans votre propre code.

Obtention et utilisation du code source


Pour obtenir le code source compagnon de ce livre, rendez-vous sur le site www.oozinoz.com, tlchargez le chier zip contenant le code source, et dcompressez-en le contenu. Vous pouvez le placer nimporte o sur votre systme de chiers. Ce code est gratuit, vous pouvez lutiliser comme bon vous semble avec pour seule condition de ne pas afrmer en tre lauteur. Dun autre ct, les auteurs et lditeur de ce livre ne garantissent en aucune faon que ce code sera utile et adquat pour quelque objectif que ce soit.

406

Partie VI

Annexes

Construction du code dOozinoz


Si vous ne disposez pas dun environnement de dveloppement pour crire des programmes Java, vous devez en acqurir un et vous familiariser avec pour pouvoir dvelopper, compiler et excuter vos propres programmes. Vous pouvez acheter un outil de dveloppement, tel quIdea dIntellij, ou recourir des outils open source, tels quEclipse.

Test du code avec JUnit


Les bibliothques Oozinoz incluent un package com.oozinoz.testing conu pour tre utilis avec JUnit, un environnement de test automatis. Vous pouvez tlcharger JUnit partir de http://junit.org. Si lemploi de cet environnement ne vous est pas familier, la meilleure faon dapprendre vous en servir est de demander de laide auprs dun ami ou dun collgue. Autrement, vous pouvez vous aider de la documentation en ligne ou trouver un livre sur le sujet. Le temps dapprentissage est moins rapide quavec Ant, par exemple, mais ltudier vous apportera des comptences qui vous seront protables pour de nombreuses annes.

Localiser les chiers


Il peut tre difcile de trouver le chier particulier correspondant un extrait de code prsent dans le livre. Le moyen le plus simple de localiser une application donne est souvent de rechercher son nom dans larborescence du rpertoire oozinoz. Lorganisation de cette arborescence devrait vous permettre de facilement localiser les chiers voulus. Le Tableau C.1 reprend les sous-rpertoires de oozinoz avec une description de leur contenu.
Tableau C.1 : Sous-rpertoires du rpertoire oozinoz

Rpertoire

Contenu Sous-rpertoires des "applications" : chiers Java sous-jacents aux programmes excutables. Ils sont gnralement organiss par chapitres. Ainsi app.decorator contient du code se rapportant au Chapitre 27, DECORATOR. Le code source dAster, une socit ctive. Le code source des applications Oozinoz. Des images utilises par diverses applications Oozinoz.

app

aster com images

Annexe C

Code source dOozinoz

407

Rsum
Les efforts investis dans lapprentissage des patterns de conception commenceront porter leurs fruits lorsque vous changerez votre faon dcrire et de refactoriser, ou restructurer, du code. Vous pourrez mme appliquer directement des patterns dans votre propre code. Russir faire fonctionner le code dOozinoz sur votre propre machine sera un exercice utile, comme dapprendre utiliser des outils open source, tels quAnt et JUnit. Apprendre ces derniers et parvenir faire tourner le code dOozinoz, ou de nimporte qui dautre, peut reprsenter un certain travail, mais les bnces tirs de ces difcults vous seront protables pendant de longues annes grce aux nouvelles comptences acquises, lesquelles seront applicables au fur et mesure de lapparition de nouvelles techniques et technologies. Bonne chance ! Si vous rencontrez des problmes ou tes bloqu, nhsitez pas crire lun de nous deux. Steve Metsker (Steve.Metsker@acm.org) William Wake (William.Wake@acm.org)

D
Introduction UML
Cette annexe est une brve introduction au langage de modlisation UML (Unied Modeling Language), que ce livre utilise. UML propose une convention de notation permettant dillustrer la conception de systmes orients objet. Ce langage nest pas excessivement complexe, mais il est facile den sous-estimer la richesse fonctionnelle. Pour une introduction rapide la plupart de ses fonctionnalits, voyez louvrage UML Distilled [Fowler et Scott 2003]. Pour une analyse plus approfondie, reportez-vous louvrage The Unied Modeling Language User Guide (le Guide de lutilisateur UML) [Booch, Rumbaugh, et Jacobsen 1999]. Lapprentissage de nomenclatures et notations standard permet de communiquer au niveau conception et dtre plus efcace.

Classes
La Figure D.1 applique certaines des fonctionnalits dUML pour illustrer des classes. Voici quelques directives concernant llaboration de diagrammes de classes :
m

Dessinez une classe en centrant son nom dans un rectangle. La Figure D.1 montre trois classes : Firework, Rocket et simulation.RocketSim. Le langage nexige pas quun diagramme doive tout montrer propos dun lment illustr, comme lensemble des mthodes dune classe ou le contenu complet dun package.

410

Partie VI

Annexes

Signalez un package en alignant son nom sur la gauche dans un rectangle adjoint un plus grand encadr pouvant illustrer des classes ou dautres types dlments. Par exemple, les classes dans la Figure D.1 se trouvent dans le package Fireworks. Lorsquune classe est reprsente en dehors dun diagramme de package, ajoutez en prxe lespace de nom (namespace) spar du nom de la classe par un point. Par exemple, la Figure D.1 montre que la classe RocketSim se trouve dans le package simulation. Vous pouvez spcier les variables dinstance dune classe dans une subdivision rectangulaire au-dessous du nom de la classe. La classe Firework possde les variables dinstance name, mass et price. Faites suivre un nom de variable dun double-point et de son type. Vous pouvez signier quune variable dinstance ou une mthode est prive en faisant prcder son nom dun signe moins (). Un signe (+) indique quelle est publique, et un signe dise (#) signale quelle est protge. Vous pouvez spcier les mthodes dune classe dans un second rectangle audessous du nom de la classe. Quand une mthode attend des paramtres en entre, vous pouvez les indiquer, comme le fait la mthode lookup() dans la Figure D.1 Dans les signatures de mthode, les variables sont spcies par leur nom suivi du signe double-point et de leur type. Vous pouvez omettre ou abrger le nom si le type suggre clairement le rle de la variable. Soulignez le nom dune mthode pour signier quelle est statique, comme cest le cas de la mthode lookup() dans la Figure D.1. Inscrivez des notes dans un encadr comportant un coin rabattu. Le texte contenu peut comprendre des commentaires, des contraintes ou du code. Reliez la note aux autres lments du diagramme par un trait en pointill. Les notes peuvent apparatre dans nimporte quel diagramme UML.

Annexe D

Introduction UML

411

fireworks

Firework -name:String -mass:double -price:Dollars +Firework( name:String, mass:double, price:Dollars) +lookup(name:String):Firework +getName() +getMass():double +getPrice()

Rocket

simulation.RocketSim

public String getName() { return name; }

Figure D.1
Le package Fireworks inclut les classes Firework et Rocket.

412

Partie VI

Annexes

Relations entre classes


La Figure D.2 illustre quelques-unes des fonctionnalits dUML servant modliser les relations inter-classes. Voici quelques directives de notation pour les relations de classes :
m

Spciez un nom de classe ou un nom de mthode en italiques pour signier que la classe ou la mthode est abstraite. Soulignez le nom pour indiquer quelle est statique. Pour indiquer une relation sous-classe/super-classe, utilisez une che tte creuse pointant vers la super-classe. Utilisez une ligne entre deux classes pour indiquer que leurs instances partagent une quelconque relation. Trs frquemment, une telle ligne signie quune classe possde une variable dinstance se rfrant une autre classe. Par exemple, la classe Machine prvoit une variable dinstance pour conserver une rfrence un objet TubMediator. Utilisez un losange pour indiquer quune instance dune classe contient une collection dinstances dune autre classe. Une che tte ouverte indique la navigabilit. Elle permet de signaler quune classe possde une rfrence une autre classe, la classe pointe par la che, mais que celle-ci ne possde pas de rfrence rciproque. Lindicateur de pluralit, tel que 0..1, indique le nombre de relations pouvant apparatre entre des objets. Utilisez le symbole astrisque (*) pour signaler que zro ou plusieurs instances dune classe peuvent tre relies dautres instances dune classe associe. Lorsquune mthode peut gnrer une exception, vous pouvez lindiquer laide dune che en pointill allant de la mthode vers la classe dexception. Etiquetez la che avec lexpression throw. Utilisez une che en pointill entre deux classes pour indiquer une dpendance nemployant pas de rfrence dobjet. Par exemple, la classe Customer sappuie sur une mthode statique du moteur de recommandation LikeMyStuff.

Annexe D

Introduction UML

413

MachineComponent

getMachineCount()

Machine

MachineComposite

getMachineCount()

getMachineCount()

0..1 TubMediator throws ArgumentNullException

set(:Tub,:Machine)

Customer

LikeMyStuff

getRecommended(): Firework

suggest( c:Customer):Object

Figure D.2
Un objet MachineComposite contient des objets Machine ou dautres objets composites. La classe Customer dpend de la classe LikeMyStuff.

414

Partie VI

Annexes

Interfaces
La Figure D.3 illustre les fonctionnalits de base servant la modlisation des interfaces.

MachineController driver:MachineDriver

interface MachineDriver

startMachine() Cloneable stopMachine()

StarPressDriver HskMachineManager2

setTimeout(:double)

ShellDriver

Figure D.3
Vous pouvez signaler une interface au moyen dune tiquette "interface" ou dun symbole ressemblant une sucette.

Voici quelques directives de reprsentation :


m

Vous pouvez signaler une interface en plaant dans un rectangle le texte "interface" et son nom, comme le montre la Figure D.3. Une che en pointill tte creuse permet dindiquer quune classe implmente linterface. Vous pouvez aussi signier quune classe implmente une interface en utilisant une ligne surmonte dun cercle (ressemblant une sucette) avec le nom de linterface. Les interfaces et leurs mthodes sont toujours abstraites en Java. Curieusement, elles napparaissent pas en italiques comme cest le cas des classes abstraites et des mthodes abstraites spcies dans des classes.

Objets
La Figure D.4 illustre un exemple de diagramme dobjets servant reprsenter des instances spciques de classes.

Annexe D

Introduction UML

415

a:Assay

b:Batch

c:Chemical

ShowClient

:Rocket

create :Rocket thrust thrust

Figure D.4
Les reprsentations dobjets mentionnent leur nom et/ou leur type. Un diagramme de squence signale une succession dappels de mthodes.

Voici quelques directives de modlisation des objets :


m

Spciez un objet en indiquant son nom et son type spars par un signe doublepoint. Vous pouvez optionnellement nindiquer que le nom ou que le signe double-point suivi du type. Dans tous les cas, soulignez le nom et/ou le type. Utilisez une ligne pour indiquer quun objet possde une rfrence un autre objet. Vous pouvez utiliser une che tte ouverte pour signaler la direction de la rfrence. Vous pouvez illustrer une squence dobjets envoyant des messages, comme illustr dans la partie infrieure de la Figure D.4. Lordre de lecture des messages est du haut vers le bas, et les lignes en pointill indiquent lexistence de lobjet dans le temps. Utilisez ltiquette "create" pour montrer quun objet en cre un autre. La Figure D.4 illustre la classe ShowClient crant un objet local Rocket.

416

Partie VI

Annexes

Encadrez un objet par une bordure paisse pour signaler quil est actif dans un autre thread, processus ou ordinateur. La Figure D.4 montre un objet local Rocket transmettant une requte relative sa mthode thrust(); un objet Rocket sexcutant sur un serveur.

Etats
La Figure D.5 illustre un diagramme dtats.

click

Closed

complete

click

Opening click complete timeout

Closing

click Open click StayOpen

Figure D.5
Un diagramme dtats montre les transitions dun tat lautre.

Voici des directives concernant la modlisation dtats :


m m m

Indiquez un tat dans un rectangle coins arrondis. Signalez une transition entre deux tats au moyen dun che tte ouverte. Un diagramme dtats na pas besoin de correspondre directement une classe ou un diagramme dobjets. Il peut toutefois reprsenter une transposition directe, comme le montre la Figure 22.3 du Chapitre 22.

Glossaire

Abstraction. Une classe qui dpend de mthodes abstraites implmentes dans des sous-classes ou dans les implmentations dune interface. Adaptateur de classe. Un ADAPTER qui tend la classe adapter et rpond aux exigences de linterface cible. Adaptateur dobjet. Un ADAPTER qui tend une classe cible et dlgue une classe existante. Algorithme. Une procdure de calcul bien dnie qui reoit un ensemble de valeurs en entre et produit une valeur en sortie. Analyse. Lanalyse dune prparation chimique. Analyseur syntaxique. Un objet qui reconnat les lments dun langage et dcompose leur structure daprs un ensemble de rgles pour les mettre dans une forme adapte un traitement subsquent. API (Application Programming Interface). Linterface, ou lensemble dappels, quun systme rend accessible publiquement. Apoge. Le point le plus lev de la trajectoire dun artice. Arbre. Un modle objet qui ne contient pas de cycles. Arbre syntaxique abstrait. Une structure, cre par un analyseur syntaxique, qui organise le texte en entre daprs la grammaire dun langage. Bombe arienne. Un artice lanc partir dun mortier et qui explose en plein vol, jectant des toiles mises feu. Carrousel. Un grand rack intelligent qui accepte des produits par une porte et les stocke.

418

Glossaire

Chandelle romaine. Un tube stationnaire qui contient un mlange de charges explosives et dtoiles. Chemin. Dans un modle objet, une srie dobjets telle que chaque objet possde une rfrence vers lobjet suivant. Client. Un objet qui utilise ou requiert les mthodes dun autre objet. Composite. Un groupe dobjets dans lequel certains objets peuvent en contenir dautres, de faon que certains objets reprsentent des groupes et dautres reprsentent des lments individuels, ou feuilles. Constructeur. Dans Java, une mthode spciale dont le nom correspond celui dune classe et qui sert instancier cette classe. Copie complte. Une copie dun objet dans laquelle les attributs du nouvel objet sont des copies compltes des attributs de loriginal. Copie partielle. Une copie dun objet dans laquelle les attributs du nouvel objet ne sont pas des copies compltes des attributs de loriginal, laissant le nouvel objet partager avec loriginal des objets subordonns. CORBA (Common Object Request Broker Architecture). Une conception standard (architecture commune) qui supporte la transmission de requtes dobjets entre systmes. Couche. Un groupe de classes possdant des responsabilits similaires, souvent runies dans une bibliothque, et prsentant gnralement des dpendances bien dnies avec dautres couches. Couplage lche. Une responsabilit mutuelle limite et bien dnie entre des objets qui interagisseant. Cycle. Un chemin le long duquel un nud, ou objet, apparat deux fois. Double dispatching. Une conception dans laquelle un objet de classe B passe une requte un objet de classe A, lequel la repasse immdiatement lobjet de classe B, avec des informations additionnelles sur le type de lobjet de classe A. Driver. Un objet qui opre sur un systme informatique, tel quune base de donnes, ou sur un quipement externe, tel quun traceur, conformment une interface bien dnie.

Glossaire

419

EJB (Enterprise JavaBeans). Une spcication darchitecture multiniveau base sur des composants. Encapsulation. Une conception qui limite, au moyen dune interface spcique, laccs aux donnes et aux oprations dun objet. Equations paramtriques. Des quations qui dnissent un groupe de variables, telles que x et y, en tant que fonctions dun paramtre standard, tel que t. Etat. Une combinaison des valeurs courantes des attributs dun objet. Etoile. Une petite bille forme partir dun mlange explosif, entrant habituellement dans la composition dune bombe arienne ou dune chandelle romaine. Feuille. Un lment individuel dans un composite. Flux. Une squence doctets ou de caractres, comme celles apparaissant dans un document. Grammaire. Un ensemble de rgles de composition. Graphe. Un ensemble de nuds et dartes. Graphe orient. Un graphe dans lequel les artes ont une direction. GUI (Graphical User Interface). Dans une application, une couche logicielle qui permet lutilisateur dinteragir avec des boutons, des menus, des barres de dlement, des zones de texte, et dautres composants graphiques. Hirarchie parallle. Une paire de hirarchies de classes dans laquelle chaque classe dune hirarchie possde une classe correspondante dans lautre hirarchie. Hook. Un appel de mthode plac par un dveloppeur dans le code pour permettre dautres dveloppeurs dinsrer du code un point spcique dune procdure. IDE (Integrated Development Environment). Un ensemble logiciel combinant des outils pour ldition et le dbogage de code et des outils pour la cration de programmes. Immuable. Qualie un objet dont les valeurs ne peuvent pas changer. Implmentation. Les instructions qui forment le corps des mthodes dune classe. Initialisation paresseuse. Linstanciation dun objet seulement au moment o il est requis.

420

Glossaire

Interface. Lensemble des mthodes et des champs dune classe auxquels des objets dautres classes sont autoriss accder. Egalement une interface Java qui dnit les mthodes que la classe dimplmentation doit fournir. Interface de marquage. Une interface qui ne dclare aucun champ ou mthode, dont la simple prsence indique quelque chose. Par exemple, Cloneable est une interface de marquage qui garantit ses implmenteurs quils supporteront la mthode clone() dnie dans Object. Interface graphique utilisateur. Voir GUI. Interprteur. Un objet compos partir dune hirarchie de composition dans laquelle chaque classe reprsente une rgle de composition dterminant comment elle implmente ou interprte une opration qui survient dans la hirarchie. JDBC. Une interface de programmation dapplications pour lexcution dinstructions SQL. JDBC est une marque, non un sigle. JDK (Java Development Kit). Un ensemble logiciel incluant des bibliothques de classes Java, un compilateur, et dautres outils associs. Dsigne souvent spciquement les kits disponibles ladresse java.sun.com. JUnit. Un environnement de test, crit par Erich Gamma et Kent Beck, qui permet dimplmenter des tests de rgression automatiss dans Java. Disponible ladresse www.junit.org. Kit. Une classe avec des mthodes de cration qui retournent des instances dune famille dobjets. Voyez le Chapitre 17, ABSTRACT FACTORY. Langage de consolidation. Un langage informatique, tel que Java ou C#, qui tente de prserver les points forts de ses prdcesseurs et dliminer leurs faiblesses. Loi de Demeter. Un principe de conception orient objet qui stipule quune mthode dun objet devrait envoyer des messages uniquement aux objets argument, lobjet lui-mme, ou aux attributs de lobjet. Mthode. Limplmentation dune opration. Modle-Vue-Contrleur. Une conception qui spare un objet intressant, le modle, des lments de GUI qui le reprsentent et le manipulent, la vue et le contrleur. Mole. Par dnition, cest la quantit de matire dun systme contenant autant dentits lmentaires quil y a datomes dans 12 grammes de carbone 12 .

Glossaire

421

Ce nombre permet dappliquer des quations chimiques tout en travaillant avec des quantits mesurables de prparations chimiques. Si mw est le poids molculaire dune entit chimique, mwgrammes de cette entit chimique contiendra un mole de cette entit chimique. Mortier. Un tube partir duquel une bombe arienne est lance. Multiniveau. Un type de systme qui assigne des couches de responsabilits des objets sexcutant sur diffrents ordinateurs. Mutex. Un objet partag par des threads rivalisant pour obtenir le contrle du verrou sur lobjet. Ce terme signie littralement exclusion mutuelle. Niveau. Une couche logicielle qui sexcute sur un ordinateur. Objet mtier. Un objet qui modlise une entit ou un processus dans une entreprise. Oozinoz. Une entreprise ctive qui fabrique et vend des pices pour feux dartices et organise des vnements pyrotechniques. Opration. La spcication dun service qui peut tre demand partir dune instance dune classe. Pattern. Un moyen daccomplir quelque chose, datteindre un objectif. Pattern de conception. Un pattern qui opre approximativement au niveau classe. Polymorphisme. Le principe selon lequel linvocation dune mthode dpend la fois de lopration invoque et du rcepteur de linvocation. Presse toiles. Une machine qui moule une prparation chimique en lui donnant la forme dtoiles. Principe de substitution de Liskov. Un principe de conception orient objet qui stipule quune instance dune classe devrait fonctionner comme une instance de sa super-classe. Racine. Dans un arbre, un nud ou objet distinct qui na pas de parent. Refactorisation. La modication de code an damliorer sa structure interne sans changer son comportement extrieur.

422

Glossaire

Rexion. La possibilit de travailler avec des types et des membres de types en tant quobjets. Relation. Le rapport quentretiennent des objets. Dans un modle objet, il sagit du sous-ensemble de toutes les rfrences possibles des objets dun type des objets dun second type. RMI (Remote Method Invocation). Une fonctionnalit Java qui permet des objets situs sur des ordinateurs diffrents de communiquer. Session. Lvnement qui consiste pour un utilisateur excuter un programme, accomplir des transactions dans ce programme, et le quitter. Signature. Une combinaison du nom dune mthode ainsi que du nombre et du type de ses paramtres formels. SQL (Structured Query Language). Un langage informatique pour interroger des bases de donnes relationnelles. Stockage persistant. Une forme de stockage sur un matriel, tel quun disque, o les informations sont prserves mme si la machine est arrte. Stratgie. Un plan, ou une approche, pour atteindre un but selon certaines conditions initiales. Thorie des graphes. Une conception mathmatique de nuds et dartes. Lorsquelle est applique un modle objet, les nuds du graphe sont gnralement des objets et ses artes sont habituellement des rfrences ces objets. Traverse post-ordonne. Une itration sur un arbre ou un autre objet composite lors de laquelle un nud est retourn aprs ses descendants. Traverse pr-ordonne. Une itration sur un arbre ou un autre objet composite lors de laquelle un nud est retourn avant ses descendants. Trmie. Un conteneur qui dispense des produits chimiques, gnralement dans une machine. Type de retour covariant. Lorsquune sous-classe remplace une mthode et dclare son type de retour comme tant une sous-classe du type de retour du parent. UML (Unied Modeling Language). Une notation permettant de reprsenter des ides de conception.

Glossaire

423

URL (Uniform Resource Locator). Un pointeur vers une ressource Web. Verrou. Une ressource exclusive qui reprsente la possession dun objet par un thread. WIP (Work In Process). Des biens en cours de fabrication dans une usine. XML (Extensible Markup Language). Un langage textuel qui utilise des balises contenant des informations relatives au texte et qui spare prcisment les classes ou types de documents de leurs instances.

Bibliographie
Alexander, Christopher. 1979. The Timeless Way of Building. Oxford, England : Oxford University Press. Alexander, Christopher, Sara Ishikawa, et Murray Silverstein. 1977. A Pattern Language: Towns, Buildings, Construction. Oxford, England : Oxford University Press. Arnold, Ken, et James Gosling. 1998. The JavaTM Programming Language, Second Edition. Reading, MA : Addison-Wesley. Booch, Grady, James Rumbaugh, et Ivar Jacobsen. 1999. The Unied Modeling Language User Guide. Reading, MA : Addison-Wesley. Buschmann, Frank, et al. 1996. Pattern-Oriented Software Architecture: A System of Patterns. Chichester, West Sussex, England : John Wiley & Sons. Cormen, Thomas H., Charles E. Leiserson, et Ronald L. Rivest. 1990. Introduction to Algorithms. Cambridge, MA : MIT Press. Cunningham, Ward, ed. The Portland Patterns Repository. www.c2.com. Flanagan, David. 2005. JavaTM in a Nutshell, 5th ed. Sebastopol, CA : OReilly & Associates. Flanagan, David, Jim Farley, William Crawford, et Kris Magnusson. 2002. JavaTM Enterprise in a Nutshell, 2d ed. Sebastopol, CA : OReilly & Associates. Fowler, Martin, Kent Beck, John Brant, William Opdyke, et Don Roberts. 1999. Refactoring: Improving the Design of Existing Code. Reading, MA : AddisonWesley. Fowler, Martin, et Kendall Scott. 2003. UML Distilled, Third Edition. Boston, MA : Addison-Wesley. Gamma, Erich, Richard Helm, Ralph Johnson, et John Vlissides. Design Patterns. 1995. Boston, MA : Addison-Wesley.

426

Bibliographie

Gosling, James, Bill Joy, Guy Steele, et Gilad Bracha. 2005. The Java TM Language Specication, Third Edition. Boston, MA : Addison-Wesley. Kerievsky, Joshua. 2005. Refactoring to Patterns. Boston, MA : Addison-Wesley. Lea, Doug. 2000. Concurrent Programming in Java TM, Second Edition. Boston, MA : Addison-Wesley. Lieberherr, Karl J., et Ian Holland. 1989. "Assuring good style for object-oriented programs". Washington, DC. IEEE Software. Liskov, Barbara. May 1987. "Data abstraction and hierarchy". SIGPLAN Notices, volume 23, number 5. Metsker, Steven J. 2001. Building Parsers with Java TM. Boston, MA : AddisonWesley. Russell, Michael S. 2000. The Chemistry of Fireworks. Cambridge, UK : Royal Society of Chemistry. Vlissides, John. 1998. Pattern Hatching: Design Patterns Applied. Reading, MA : Addison-Wesley. Wake, William C. 2004. Refactoring Workbook. Boston, MA : Addison-Wesley. Weast, Robert C., ed. 1983. CRC Handbook of Chemistry and Physics, 63rd ed. Boca Raton, FL : CRC Press. White, Seth, Mayderne Fisher, Rick Cattell, Graham Hamilton, et Mark Hapner. 1999. JDBC TM API Tutorial and Reference, Second Edition. Boston, MA : AddisonWesley. Wolf, Bobby. 1998. "Null object", in Pattern Languages of Program Design 3, ed. Robert Martin, Dirk Riehle, et Frank Buschmann. Reading, MA : Addison-Wesley.

Index
A ABSTRACT FACTORY et FACTORY METHOD 178 et packages 182 kits de GUI 173 objectif 173 solutions aux exercices 379 Abstractions BRIDGE 61 drivers 66 Adaptateurs dobjets 25 de classes 25 ADAPTER adaptateurs de classes et dobjets 25 identication 33 objectif 21 pour des donnes JTable 29 pour des interfaces 21 solutions aux exercices 338 Algorithmes 207 compltion 215 de tri 211 et mthodes 207 vs stratgies 235 Alternances et VISITOR 327 vs squences 56 Analyseurs syntaxiques INTERPRETER (pour) 265 pour BUILDER 158 VISITOR (pour) 329 Annulation doprations 189 Applications orientes objet 35 Arbres dans COMPOSITE 50 syntaxiques abstraits 329 Artes de graphes 50 Arrays (classe) 212 ASP.NET (Active Server Pages for .NET)
122

Attributs changeants, objets 223 comparaison dans un algorithme de tri 212 dun objet copi 185 B Boucles for 296 innies 57 BRIDGE abstraction 61 drivers 66 JDBC 67 objectif 61 solutions aux exercices 348 BufferedWriter (classe) 278 BUILDER avec des contraintes 160 objectif 157 ordinaire 157 solutions aux exercices 373 tolrant 163

428

Index

C CHAIN OF RESPONSABILITY ancrage 140 objectif 135 ordinaire 135 refactorisation (pour appliquer) 137 sans COMPOSITE 142 solutions aux exercices 364 Chanes analyse syntaxique 157 immuables 144 Chargeur de classe, PROXY 128 Chemin, COMPOSITE 51 Classes adaptateurs (de) 25 chargeur (de) 128 constructeurs (de) 153 de contrle 62 de ltrage 279 de handshaking 64 extension 269 familles (de) 182 hirarchies 61 hirarchies parallles 169 implmentation 15 instances uniques 79 instanciation 167 interfaces 15 modlisation UML 409 pour des couches de code 92 stub 18 visibilit 75 Classes abstraites adaptateurs dobjets (pour des) 29 dans des hirarchies 61 et interfaces 16 pour des classes de ltrage 279 Clients dnition des exigences dans une interface
25

en tant que listeners, Swing 86 enregistrement pour notications 193

et proxies 122 objets (en tant que) 21 partage dobjets (entre) 143 Clones de collections 302 prototypage (avec des) 185 Cohrence relationnelle 107 Collections boucle for 296 clones 302 itrateurs 165, 297 pour la scurit inter-threads 297 tri 212 Collections (classe) 212 COMMAND commandes de menus 245 en relation avec dautres patterns 251 fourniture dun service 248 hooks 249 objectif 245 solutions aux exercices 393 Commandes de menus 245 Comparaisons conditions boolennes 262 dans des algorithmes de tri 212 Comparator (interface) 212 Comportement rcursif dans les composites 48 COMPOSITE arbres 50 comportement rcursif 48 cycles 51, 55 feuilles 47 groupes dobjets 47 itrateurs 303 non-arbre 59 objectif 47 ordinaire 47 solutions aux exercices 345 traverses pr-ordonne/post-ordonne 303

Index

429

Concurrence, POO 81 Conditions boolennes 261 Consolidation, langage (de) 7 Constantes, dclaration dans une interface 19 Constructeurs de classes 153 Construction 153 avec des contraintes 160 ds 153 progressive dobjets 157 solutions aux exercices 371 Contraintes de construction 160 Contrat dune interface 17 entre les dveloppeurs 221 Contrleurs, conception MVC 90 Copie complte 383 de collections 302 de prototypes 185 partielle 383 CORBA (Common Object Request Broker Architecture) 122 Corps de mthodes 204 Couches de code, conception MVC 92 Couplage lche, MEDIATOR 105 Cycles consquences 59 dans COMPOSITE 51, 55 et VISITOR 323 D DECORATOR en relation avec dautres patterns 292 enveloppeurs de fonctions 285 ux et objets dcriture 277 objectif 277 solutions aux exercices 399

Dcouplage 19 Demeter, loi (de) 271 Dmos 36, 342 Dpendances, OBSERVER 98 Diagramme de squence UML 68 Double dispatching, VISITOR 322 Drivers de base de donnes 67 en tant que BRIDGE 66 Dure de vie, MEMENTO 196 E Eclipse (IDE) 35 Encapsulation 77 En-ttes de mthodes 204 Enveloppeurs de fonctions 285 Environnement de dveloppement intgr 35 Equations paramtriques 40 Erreurs potentielles, limination 273 Etats attributs changeants 223 constants 231 dans MEMENTO 189 machine () 225 modlisation 223, 416 Exceptions dclaration 206 distantes 123 gnration 206 gestion 206 Extensions 269 indicateurs 273 loi de Demeter 271 principe de substitution de Liskov 270 solutions aux exercices 398

430

Index

F FACADE dmos 36 quations paramtriques 40 objectif 35 refactorisation (pour appliquer) 37 solutions aux exercices 342 utilitaires 36 FACTORY METHOD contrle du choix de la classe instancier
167

G Grammaire dun langage de programmation 266 Graphes orients 51 GUI (Graphical User Interface) conception Modle-Vue-Contrleur (MVC) 90 et MEDIATOR 101 et OBSERVER 85 kits (de) 173 pour des commandes de menus 245 pour une application de visualisation 175 H Handshaking, classes (de) 64 Hirarchies de classes 61 extensions 315 parallles 169 stabilit 328 Hooks pour COMMAND 249 pour TEMPLATE METHOD 218 I IDE (Integrated Development Environment) 35 Identication de ADAPTER 33 de FACTORY METHOD 166 de SINGLETON 82 Immuabilit, FLYWEIGHT 143 Implmentations doprations abstraites 61 de classes 15 par dfaut 29 vides 18 Indicateurs 273

dans une hirarchie parallle 169 et ABSTRACT FACTORY 178 identication 166 itrateurs 165 objectif 165 solutions aux exercices 375 Familles dobjets 179 de classes 182 Feuilles dans COMPOSITE 47 numration 311 FileWriter (classe) 278, 280 Filtres de ux 279 Flux dE/S 277 FLYWEIGHT objectif 143 objets immuables 143 partage dobjets 146 refactorisation (pour appliquer) 144 solutions aux exercices 368 Fonctions boolennes 404 enveloppeurs (de) 285 for (boucle) 295 foreach (boucle) 295

Index

431

Initialisation paresseuse de planicateurs 220 de singletons 80 Instances initialisation paresseuse 80 principe de substitution de Liskov 270 uniques 79 Intgrit relationnelle 107 Interfaces adaptation ( des) 21 contrat 17 de marquage 338 et classes abstraites 16 et proxies dynamiques 128 Java 15 modlisation UML 414 obligations 17 pour VISITOR 330 INTERPRETER exemple 254 langages et analyseurs syntaxiques 265 objectif 253 solutions aux exercices 396 Inverse dune relation binaire 107 InvocationHandler (interface) 129 ITERATOR ajout dun niveau de profondeur un numrateur 310 avec scurit inter-threads 297 numration des feuilles 311 objectif 295 ordinaire 295 pour un composite 303 solutions aux exercices 401 traverses pr-ordonne/post-ordonne 303 J JavaBeans 122 JDBC 67

JDK (Java Development Kit) 29 JTable (adaptation pour) 29 JUnit 248, 406 K Kits doutils 35 de GUI 173 L Langage de consolidation 7 Liskov, principe de substitution (de) 270 Listeners, Swing 86, 246 Loi de Demeter 271 M Machine tats UML 225 Marquage, interfaces (de) 338 MEDIATOR objectif 101 pour lintgrit relationnelle 106 pour les GUI 101 refactorisation (pour appliquer) 104 solutions aux exercices 359 MEMENTO annulation doprations 189 dure de vie 196 informations dtat 189 objectif 189 persistance entre les sessions 197 solutions aux exercices 384 Menus commandes (de) 245 Java 246 Mthodes 203 constructeurs 153 corps 204 dans des classes stub 18

432

Index

Mthodes (suite) en-tte 204 et algorithmes 207 et polymorphisme 208 gnration dexceptions 206 hooks 218, 249 loi de Demeter 271 minutage 248 modles 211 modicateurs daccs 204 signatures 205 utilitaires 39 visibilit 75 Modle objet 51 relation (de) 107 vs modle relationnel 107 Modle-Vue-Contrleur (MVC) pour MEMENTO 191 pour OBSERVER 90 Modlisation dtats 223, 416 dinterfaces 414 dobjets 414 de classes 409 de composites 60 de conditions boolennes 261 de relations inter-classes 412 de stratgies 236 UML 409 Modicateurs daccs 76, 204 Mutex (objet) 301 N Nuds dans des graphes 50 dans des itrations 303 enfants 303 parents 51 ttes 303

O Objets adaptateurs (d) 25 clients 21 composites 47 encapsulation 77 tat 223 excutables 253 familles (d) 179 feuilles 47 immuables 143 mtiers 92 modlisation UML 414 mutex 301 paires ordonnes 106 partage 143 racines 140 rfrences 51 responsabilit 73, 79 uniques 82 verrous 81 Obligations des interfaces 17 Observable (classe) 92 OBSERVER approches push/pull 97 dans les GUI 85 maintenance 96 Modle-Vue-Contrleur 90 objectif 85 refactorisation (pour appliquer) 88 solutions aux exercices 354 Oprations abstraites 61 annulation 189 et mthodes 203 signatures 205 solutions aux exercices 387 stratgiques 235 Outils, kits (d) 35

Index

433

P Packages dans ABSTRACT FACTORY 182 kits doutils 35 Paires ordonnes dobjets 106 Paradigmes classe/instance 7 concurrents 7 Parents, nuds 51 Partage dobjets 143 Patterns 3 Patterns de conception 4 classication 9 construction 153 extensions 269 interfaces 15 oprations 203 responsabilit 73 Polymorphisme 208, 230, 245 Principe de substitution de Liskov 270 Procdures Voir Algorithmes Produit cartsien 107 Programmation oriente aspect (POA) 132 Programmation oriente objet (POO) avec concurrence 81 limination des erreurs potentielles 273 encapsulation 77 loi de Demeter 271 polymorphisme 208 principe de substitution de Liskov 270 PROTOTYPE en tant quobjet factory 183 objectif 183 pour des clones 185 solutions aux exercices 382 PROXY distant 122 dynamique 128

objectif 115 pour des images 115 solutions aux exercices 362 suppression 120 Pull, OBSERVER 97 Push, OBSERVER 97 R Racines dans CHAIN OF RESPONSABILITY 140 dans des graphes 51 Refactorisation pour appliquer CHAIN OF RESPONSABILITY 137 pour appliquer FACADE 37 pour appliquer FLYWEIGHT 144 pour appliquer MEDIATOR 104 pour appliquer OBSERVER 88 pour appliquer STATE 227 pour appliquer STRATEGY 238 pour appliquer TEMPLATE METHOD 219 Rfrences dobjets 51 Rexion dans PROXY 129 pour linstanciation 154 Registre RMI 124 Relations entre objets 106 repeat (boucle) 295 Responsabilit codage en couches 95 contrle par la visibilit 75 couplage lche 105 distribution vs centralisation 77 et encapsulation 77 objets 79 ordinaire 73 solutions aux exercices 350 Rutilisabilit des kits doutils 35 RMI (Remote Method Invocation) 122

434

Index

S Scurit inter-threads 297 Squences dinstructions 211 et VISITOR 327 vs alternances 56 Services fourniture avec COMMAND 248 spcication 203 Sessions 197 Signatures de mthodes 205 SINGLETON et threads 81 identication 82 mcanisme 79 objectif 79 solutions aux exercices 353 Solutions aux exercices ABSTRACT FACTORY 379 ADAPTER 338 BRIDGE 348 BUILDER 373 CHAIN OF RESPONSABILITY 364 COMMAND 393 COMPOSITE 345 construction 371 DECORATOR 399 extensions 398 FACADE 342 FACTORY METHOD 375 FLYWEIGHT 368 interfaces 337 INTERPRETER 396 ITERATOR 401 MEDIATOR 359 MEMENTO 384 OBSERVER 354 oprations 387 PROTOTYPE 382 PROXY 362 responsabilit 350 SINGLETON 353 STATE 390

STRATEGY 392 TEMPLATE METHOD 389 VISITOR 403 Sous-classes anonymes 94 de classes stub 18 pour des adaptateurs de classes 25 pour INTERPRETER 257 STATE et STRATEGY 242 tats constants 231 modlisation dtats 223 objectif 223 refactorisation (pour appliquer) 227 solutions aux exercices 390 Stockage persistant 197 STRATEGY et STATE 242 et TEMPLATE METHOD 243 modlisation de stratgies 236 objectif 235 refactorisation (pour appliquer) 238 solutions aux exercices 392 vs algorithmes 235 Stub, classes 18 Super-classes 24 Swing 245 et OBSERVER 86 listeners 246 widget JTable 29 Systmes multiniveaux 96 T Tableaux adaptation une interface 29 et boucles for 296 tri 212 TEMPLATE METHOD algorithmes de tri 211 compltion dun algorithme 215 et STRATEGY 243

Index

435

hooks 218 objectif 211 refactorisation (pour appliquer) 219 solutions aux exercices 389 Texte analyse syntaxique 265 ltres de mise en forme 280 passage la ligne 283 Thorie des graphes 51 Threads dans SINGLETON 81 pour le chargement dimages 118 scurit (inter-) 297 verrous 81 throws (clause) 204 Traverses pr-ordonne/post-ordonne 303 Tri, algorithmes (de) 211 Types de retour covariants 388 de mthodes 205 U UML (Unied Modeling Language) 409 diagramme de squence 68 machine tats 225

notation 7 UnicastRemoteObject (interface) 123 Utilitaires, FACADE 36 V Verrous dans une application multithread 301 sur des objets 81 Visibilit des classes et des mthodes 75 et responsabilit 75 VISITOR application (de) 315 double dispatching 322 et cycles 323 inconvnients 328 objectif 315 ordinaire 318 solutions aux exercices 403 Vues, conception MVC 90 W while (boucle) 295 WIP (Work In Process) 82

Rfrence

Les design patterns en Java


Les 23 modles de conception fondamentaux
Tout programmeur Java se doit de connatre les 23 design patterns fondamentaux recenss par les clbres dveloppeurs du Gang of Four, vritable condens de lexprience de plusieurs gnrations de dveloppeurs, et aujourdhui incontournable pour crire un code propre et efcace. Cet ouvrage, fond sur de nombreux exemples dapplication, vous aidera comprendre ces modles et dveloppera votre aptitude les appliquer dans vos programmes. Forts de leur exprience en tant quinstructeurs et programmeurs Java, Steve Metsker et William Wake vous claireront sur chaque pattern, au moyen de programmes Java rels, de diagrammes UML, de conseils sur les bonnes pratiques et dexercices clairs et pertinents. Vous passerez rapidement de la thorie lapplication en apprenant comment crire un meilleur code ou restructurer du code existant pour le rationaliser, le rendre plus performant et plus facile maintenir. Code source disponible sur le site www.oozinoz.com !
propos des auteurs : Steven John Metsker, spcialiste des techniques orientes objet sousjacentes la cration de logiciels purs et performants, est lauteur des ouvrages Building Parsers with Java, Design Patterns Java Workbook et Design Patterns in C# (parus chez Addison-Wesley). Il est dcd en fvrier 2008. William C. Wake (www.xp123.com) est consultant logiciel, coach et instructeur indpendant, avec plus de vingt annes dexprience en programmation. Il est lauteur de Refactoring Workbook et Extreme Programming Explored (parus chez Addison-Wesley).
TABLE DES MATIRES

Introduction aux interfaces ADAPTER FACADE COMPOSITE BRIDGE Introduction la responsabilit SINGLETON OBSERVER MEDIATOR PROXY CHAIN OF RESPONSABILITY FLYWEIGHT Introduction la construction BUILDER FACTORY METHOD ABSTRACT FACTORY PROTOTYPE MEMENTO Introduction aux oprations TEMPLATE METHOD STATE STRATEGY COMMAND INTERPRETER Introduction aux extensions DECORATOR ITERATOR VISITOR Code source dOozinoz Introduction UML

Programmation

Niveau : Avanc Conguration : Multiplate-forme

Pearson Education France 47 bis, rue des Vinaigriers 75010 Paris Tl. : 01 72 74 90 00 Fax : 01 42 05 22 17 www.pearson.fr

ISBN : 978-2-7440-4097-9

Vous aimerez peut-être aussi