• Aucun résultat trouvé

[PDF] Cours Java exemples et explications pour maitriser la programmation | Cours java

N/A
N/A
Protected

Academic year: 2021

Partager "[PDF] Cours Java exemples et explications pour maitriser la programmation | Cours java"

Copied!
109
0
0

Texte intégral

(1)

Arnaud BARBE Page 1 sur 109

Java entreprise et les design

patterns

Version 2.0

(2)

1

OBJECTIF

6

2

PATTERNS

7

2.1

OBJECTIFS

7

2.2

DESCRIPTION

7

2.3

AVANTAGES

7

2.4

INCONVENIENTS

7

2.5

GENERAL RESPONSABILITY ASSIGNEMENT SOFTWARE PATTERNS [GRASP]

8

2.5.1

PRESENTATION

8

2.5.2

PATTERNS FONDAMENTAUX

8

2.5.2.1

Expert

8

2.5.2.2

Low coupling

9

2.5.2.3

High cohesion

10

2.5.2.4

Creator

11

2.5.3

PATTERNS SPECIFIQUES

11

2.5.3.1

Controller

11

2.5.3.2

Polymorphism

12

2.5.3.3

Pure Fabrication

13

2.5.3.4

Indirection

14

2.5.3.5

Law of Demeter

14

2.6

DESIGN PRINCIPLES

15

2.6.1

PRESENTATION

15

2.6.2

GESTION DES EVOLUTIONS ET DES DEPENDANCES ENTRE CLASSES

15

2.6.2.1

Open/Closed principle

15

2.6.2.2

Liskov substitution principle

16

2.6.2.3

Dependency inversion principle

16

2.6.2.4

Interface segregation principle

17

2.6.3

ORGANISATION DE L’APPLICATION EN MODULES

18

2.6.3.1

Reuse/Release equivalence principle

18

2.6.3.2

Common reuse principle

19

2.6.3.3

Common closure principle

19

2.6.4

GESTION DE LA STABILITE DE L’APPLICATION

20

2.6.4.1

Acyclic dependencies principle

20

2.6.4.2

Stable dependencies principle

20

2.6.4.3

Stable abstractions principle

21

2.6.5

CONCLUSION

21

2.7

GANG OF FOUR [GOF]

22

2.7.1

PRESENTATION

22

2.7.2

PATTERNS DE CREATION

22

2.7.2.1

Abstract Factory 

22

2.7.2.2

Builder 

23

2.7.2.3

Factory Method 

24

2.7.2.4

Prototype 

26

2.7.2.5

Singleton 

27

2.7.3

PATTERNS DE STRUCTURE

28

2.7.3.1

Adapter 

28

2.7.3.2

Bridge 

29

(3)

2.7.3.6

FlyWeight 

34

2.7.3.7

Proxy 

37

2.7.4

PATTERNS DE COMPORTEMENT

38

2.7.4.1

Chain of Responsability 

38

2.7.4.2

Command 

39

2.7.4.3

Interpreter 

41

2.7.4.4

Iterator 

42

2.7.4.5

Mediator 

43

2.7.4.6

Memento 

44

2.7.4.7

Observer 

45

2.7.4.8

State 

47

2.7.4.9

Strategy 

48

2.7.4.10

Template Method 

50

2.7.4.11

Visitor 

51

2.8

PATTERN JEE

52

2.8.1

BUSINESS DELEGATE

52

2.8.1.1

Problème

52

2.8.1.2

Solution

52

2.8.1.3

Implémentation

53

2.8.2

SERVICE LOCATOR

53

2.8.2.1

Problème

53

2.8.2.2

Solution

54

2.8.2.3

Implémentation

55

2.8.3

SESSION FACADE

56

2.8.3.1

Problème

56

2.8.3.2

Solution

56

2.8.3.3

Implémentation

57

2.8.4

MESSAGE FACADE / SERVICE ACTIVATOR

57

2.8.4.1

Problème

57

2.8.4.2

Solution

58

2.8.4.3

Implémentation

58

2.8.5

TRANSFER OBJECT / VALUE OBJECT

58

2.8.5.1

Problème

58

2.8.5.2

Solution

59

2.8.5.3

Implémentation

59

2.8.6

TRANSFER OBJECT ASSEMBLER / VALUE OBJECT ASSEMBLER

60

2.8.6.1

Problème

60

2.8.6.2

Solution

60

2.8.6.3

Implémentation

61

2.8.7

VALUE LIST HANDLER

61

2.8.7.1

Problème

61

2.8.7.2

Solution

61

2.8.7.3

Implémentation

62

2.8.8

DATA ACCESS OBJECT

62

2.8.8.1

Problème

62

2.8.8.2

Solution

62

2.8.8.3

Implémentation

62

2.8.9

CONCLUSION

63

3

ANTI-PATTERNS

64

3.1

CODE SOURCE

64

3.1.1

CODE COURT

64

3.1.2

OPTIMISATION

64

(4)

3.1.3

COPIER/COLLER

64

3.1.4

VARIABLES

64

3.1.5

CONCATENATION DE CHAINES DE CARACTERES

64

3.1.6

COHERENCE DU SYSTEME

64

3.1.7

TYPAGE FORT/FAIBLE

65

3.1.8

SYNCHRONIZED

66

3.1.9

DOCUMENTATION

67

3.1.10

EXCEPTION

67

3.1.11

PARAMETRES DE METHODES

67

3.2

PRINCIPES

68

3.2.1

RESSOURCES

68

3.2.2

HERITAGE

68

3.2.3

HABITUDE

69

3.2.4

SIMPLICITE

69

3.3

MEMOIRE

69

3.3.1

DUREE DE VIE DES OBJETS

69

3.3.2

ALLOCATION MEMOIRE

69

3.4

CONCEPTION

69

3.4.1

THE GOD CLASS

69

3.4.2

SWISS ARMY KNIFE

70

3.4.3

LAVA FLOW

70

3.4.4

NO OO

70

3.4.5

PROLIFERATION OF CLASSES

70

3.4.6

GOLDEN HAMMER

71

3.4.7

SPAGHETTI CODE

71

3.4.8

REINVENT THE WHEEL

71

4

CONCLUSION

72

4.1

A RETENIR

72

4.2

ET APRES ?

72

4.3

CODE SOURCE

72

4.3.1

CREATION

72

4.3.1.1

AbstractFactory

72

4.3.1.2

Builder

74

4.3.1.3

FactoryMethod

75

4.3.1.4

Prototype

76

4.3.1.5

Singleton

76

4.3.2

STRUCTURE

77

4.3.2.1

Adapter

77

4.3.2.2

Bridge

77

4.3.2.3

Composite

79

4.3.2.4

Decorator

81

4.3.2.5

Facade

83

4.3.2.6

Flyweight

84

4.3.2.7

Proxy

86

4.3.3

COMPORTEMENT

88

4.3.3.1

Chain of responsability

88

4.3.3.2

Command

89

4.3.3.3

Interpreter

91

4.3.3.4

Iterator

94

4.3.3.5

Mediator

95

(5)

4.3.3.8

State

100

4.3.3.9

Strategy

103

4.3.3.10

Template Method

105

4.3.3.11

Visitor

105

(6)

1

OBJECTIF

Ce document est un condensé de ce qui aujourd’hui (et pour moi) semble essentiel dans le monde java et celui des systèmes d’informations d’entreprises. Il est orienté conception et (l’un ne va pas sans l’autre) design patterns. C’est un résumé de mes expériences professionnelles passées, des articles et livres lus. Mon travail n’a pas été de créer un énième article ou livre sur java et l’entreprise mais plutôt de regrouper un savoir éparpillé au travers d’écrits de personnalités brillantes comme Bertrand Meyer, Craig LARMAN, Robert MARTIN, la bande des quatre et les autres (pardon pour ceux que j’oublie).

Par avance je m’excuse des risques de plagiat difficile à éviter dans ce type d’exercice de transcription et de résumé. D’ailleurs, rendons à césar ce qui est à césar, je ne m’attribue en aucune sorte le travail des personnes cités précédemment : je le répète mon travail se limite uniquement à condenser des concepts ou des principes.

(7)

2

PATTERNS

2.1

Objectifs

Ce chapitre a pour but de présenter les Patterns au travers de 4 approches : les patterns fondamentaux GRASP défini par Craig LARMAN, les « design principles » de Bertrand MEYER et Robert MARTIN, nous aborderons ensuite les incontournables patterns du Gang Of Four (GOF) développés par Erich Gamma, Richard Helm, Ralph Johnson et John Vlissides pour terminer avec les patterns JEE c'est-à-dire appliqués à la plateforme Java EE. Nous terminerons par un chapitre sur les anti-patterns les plus courants. Les patterns seront décrit (section présentation) puis agrémentés (section implémentation) de diagrammes adaptés au monde Java avec des exemples concrets.

2.2

Description

Les designs patterns ont pour principal intérêt de décrire et de formaliser des problèmes récurrents. Ils synthétisent le savoir-faire et l'expérience de leurs concepteurs face à des problèmes bien définis.

Ils apportent un début de réponse à votre problème. Attention cependant à ne pas les confondre avec un catalogue de solutions toutes prêtes : les design patterns constituent plutôt un guide. L’application d’un pattern à une solution passe par une période d’adaptation au problème. C’est comme si vous utilisiez des briques pour fabriquer un mur : vous possédez une forme de base qui va vous permettre de construire plus vite que si vous aviez à fabriquer les briques vous-même. Cependant lorsque le mur amorce un angle à 45°, il faudra découper certaines de vos briques. Globalement vous gagnez du temps, mais il faut adapter.

Par conséquent, dans un contexte où les applications sont de plus en plus complexes, l'utilisation des design patterns est un moyen de réduire les risques d'échec, puisque les solutions proposées ont été "testées et approuvées" dans de nombreux projets. Les design patterns peuvent être utilisés soit directement, en se conformant aux concepts et aux règles qui les caractérisent, soit par l'intermédiaire de Frameworks qui les utilisent. Ils accélèrent la conception et les développements tout en accroissant la qualité générale des applications produites, qualité indispensable à la maintenabilité et aux transferts de compétence.

2.3

Avantages

L’élévation du niveau d’abstraction est le principal intérêt : moins de perte de temps en explication, la complexité en est réduite. Le langage est commun et permet de représenter des systèmes complexes sans se perdre dans les méandres de la technique et du langage. Ils offrent une étape supplémentaire entre la conception et le code source. Globalement la qualité s’en trouve amélioré puisqu’on s’appuie sur des concepts éprouvés.

2.4

Inconvénients

Le principal ennemi du design pattern c’est …le design pattern. En effet la construction par patron demande un apprentissage de longue haleine, une habitude de lecture (issue essentiellement de l’expérience) pour pouvoir les repérer ou les appliquer. Une application peut rapidement utiliser plusieurs dizaines voir des centaines d’occurrences de pattern. A ce stade ils ont plutôt tendance à se « noyer dans la masse » et ils deviennent difficiles à reconnaître. Il faut donc être prudent et soigneux quant à la répartition en package et à la dénomination des classes pour faciliter leur localisation. La problématique est de trouver à chaque fois le juste dosage entre l’intérêt de l’utilisation d’un pattern et les inconvénients inhérents à son application.

(8)

2.5

General

Responsability

Assignement

Software

Patterns [GRASP]

2.5.1

Présentation

Le GRASP pour « patterns généraux d’affectation des responsabilités », propose des patterns fondamentaux, issus essentiellement du bon sens du développeur. Ce sont des patterns simples et basiques. Ils peuvent paraître « simplistes » pour des développeurs confirmés, mais ils sont la base de toute bonne conception. D’ailleurs, nombre d’entre vous auront une impression de déjà vu en les découvrant puisque nous les appliquons inconsciemment. Contrairement aux patterns du GOF qui se rapprochent de cas concrets, les patterns GRASP sont plus abstraits. Ils ne sont en aucun cas des modèles ou design patterns mais plutôt des principes de bonne conduite.

On distingue 9 patterns : les 4 premiers sont fondamentaux (Expert, Low coupling, High cohesion, Creator), les 5 suivants sont plus spécifiques (Controller, Polymorphism, Pure Fabrication, Indirection, Law of demeter).

2.5.2

Patterns Fondamentaux

2.5.2.1

Expert

2.5.2.1.1

Théorie

Le pattern expert propose de déterminer les responsabilités en faisant appel au bon sens du concepteur. Il s’agit d’affecter les responsabilités à une classe en fonction des informations dont elle dispose (principe d’encapsulation) ou non (principe de collaboration). Dans le cas d’une collaboration, plusieurs « experts fragmentaires » (ou classes) sont identifiés et collaborent ensemble afin de remplir la responsabilité entière. Avec ce pattern les responsabilités seront donc mis avec les données.

2.5.2.1.2

Implémentation

Dans l’exemple ci-dessous, nous effectuons une addition. Le client instancie un objet Addition et prend la responsabilité de l’opération.

Addition

01 package com.arnbe.patterns.grasp.expert.bad; 02

03 public class Addition {

04 private int firstOp;

05 private int secondOp;

06 07 /** 08 * constructeur 09 * @param op1 10 * @param op2 11 */

12 public Addition(int op1, int op2) {

13 super(); 14 firstOp = op1; 15 secondOp = op2; 16 } 17 18 /** 19 * @return 20 */

21 protected int getFirstOp() {

22 return firstOp; 23 } 24 25 /** 26 * @return 27 */

28 protected int getSecondOp() {

29 return secondOp;

30 } 31 } Client

(9)

07 int result = add.getFirstOp() + add.getSecondOp(); 08 System.out.println(result);

09 } 10 }

Ce procédé n’est pas bon car il ne respecte pas la répartition des responsabilités. En effet l’opération d’addition revient à la classe Addition et non au client. L’opération est ici simple (une addition), mais dans le cas d’une opération plus complexe, le même code devra être maintenu dans différent partie de l’application, entraînant des problèmes de maintenance, de test et voir de performance.

Il serait plus correct d’écrire : Addition

01 package com.arnbe.patterns.grasp.expert.good; 02

03 public class Addition { 04 private int firstOp; 05

06 private int secondOp; 07 08 /** 09 * constructeur 10 * @param op1 11 * @param op2 12 */

13 public Addition(int op1, int op2) { 14 super(); 15 firstOp = op1; 16 secondOp = op2; 17 } 18 19 /** 20 * @return 21 */

22 protected int getFirstOp() { 23 return firstOp; 24 } 25 26 /** 27 * @return 28 */

29 protected int getSecondOp() { 30 return secondOp; 31 }

32 33 /**

34 * additionne et retourne le résultat

35 * @return

36 */

37 public int add() {

38 return firstOp + secondOp;

39 } 40 } Client 01 package com.arnbe.patterns.grasp.expert.good; 02 03

04 public class Client {

05 public static void main(String[] args) { 06

07 Addition add = new Addition(4, 8); 08 System.out.println(add.add()); 09 }

10 }

2.5.2.2

Low coupling

2.5.2.2.1

Théorie

Le pattern « faible couplage » propose de limiter le couplage entre objets. On entend par couplage fort une association (délégation ou composition) ou un héritage. Les interdépendances (ou couplage fort) entre éléments d’un système rendent la compréhension plus difficile et la réutilisation quasi impossible. Un couplage faible facilite la réutilisation et la maintenance des éléments du système avec peu d’effet de régression (principe de modularité). Ici il convient de trouver le juste compromis entre des classes très dépendantes et un système peu communicant.

2.5.2.2.2

Implémentation

Dans le diagramme de classe suivant, un client possède des factures, et ces factures peuvent être imprimées.

(10)

Figure 1 le Customer est couplé avec Invoice mais aussi avec le Printer

Il est préférable que l’Invoice ait la capacité de s’imprimer et que Customer ne connaisse pas l’implémentation (utilisation de la classe Printer). Le diagramme suivant est plus proche de la bonne solution.

Figure 2 Couplage plus faible

2.5.2.3

High cohesion

2.5.2.3.1

Théorie

Le pattern de forte cohésion tente de répondre à la problématique d’affectation des responsabilités tout en conservant une complexité acceptable du système. Le but est de séparer les responsabilités en domaine (fonctionnel, technique, etc.) afin de préserver la cohésion de la classe. Finalement une classe cohésive est plus facile à comprendre car elle a un but bien spécifique (assuré par la finesse de l’affectation des responsabilités) et donc facilement évolutive. Attention à une cohésion trop forte (multiplication des classes) qui entraîne un couplage élevé (beaucoup de classes collaborent entre elles). A l’inverse, une classe peu cohésive est difficile à maintenir, à comprendre, à réutiliser et est globalement plus fragile car sensible à la variation.

2.5.2.3.2

Implémentation

Imaginons que notre système précédent doit assurer la persistance, la gestion et l’affichage du Customer et de l’Invoice.

Figure 3 Exemple de faible cohésion

Dans cet exemple, la classe Customer doit endosser la responsabilité de l’affichage (openCustomerWindow()), de la gestion et de la persistance de l’objet Customer (saveCustomerInDB()). La cohésion de la classe est faible et les responsabilités trop concentrées. Ce type de classe est souvent pénible à maintenir car chaque nouvelle fonctionnalité rend la classe

(11)

Figure 4 Attention à une trop forte cohésion !!

Ici la cohésion est poussée à l’extrême, mais démontre bien les risques liés à une cohésion trop forte. Toutefois si vous prenez l’option de rendre vos classes fortement cohésives, il est possible d’en réduire le couplage avec le « monde extérieur » en utilisant une façade (cf. pattern Facade du GOF).

2.5.2.4

Creator

2.5.2.4.1

Théorie

Le pattern Creator est relativement proche de l’Expert (en terme de répartition des responsabilités), à une nuance près : le Creator possède uniquement la responsabilité de la création d’un l’objet. Ceci va d’ailleurs dans le sens du pattern Low Coupling puisque la responsabilité de la création peut être attribuée à une classe dédiée. En effet la création d’un objet peut nécessiter la collaboration de plusieurs classes : dans ce cas seul le Creator possède des dépendances multiples (cf. pattern abstract factory ou builder du GOF).

2.5.2.4.2

Implémentation

Dans le diagramme de collaboration suivant, Customer prend la responsabilité de la création des Invoice qui prend à son tour la responsabilité de la création du Printer.

Figure 5 Répartition de la responsabilité de création

2.5.3

Patterns Spécifiques

2.5.3.1

Controller

2.5.3.1.1

Théorie

Le pattern Controller propose de prendre en charge la responsabilité de la réception et du traitement des messages systèmes. Par message système on entend tout message généré par un acteur externe. Le Controller peut déléguer des tâches à d’autres objets, sont but étant plutôt de coordonner les actions et les évènements. Il existe plusieurs types de Controller :

(12)

 Facade Controller : point d’entrée unique vers un système ou sous système.

 Use Case Controller : objet de contrôle pour un évènement système particulier.

 Role Controller : élément du domaine ayant le rôle du contrôleur.

Finalement, il est préférable d’utiliser des classes Controller avec une cohésion forte : la classe ne s’occupe que du traitement du message (redirection) et non de la logique métier associée (cf. pattern Facade du GOF).

2.5.3.1.2

Implémentation

Dans le diagramme ci dessous l’implémentation d’un Facade Controller faisant l’interface entre le client et un système sous jacent complexe et fortement couplé. L’intérêt est de limiter le couplage du client vers le sous système et ainsi de faciliter la réutilisation ou le remplacement du sous système par un autre. Il normalise par un point de passage de unique, les communications vers un système.

Figure 6 Limitation du couplage par utilisation d’un Controller

2.5.3.2

Polymorphism

2.5.3.2.1

Théorie

Ce pattern utilise la variation de comportement de classe en fonction de leur type. Il apporte une solution pour simuler des comportements ou des états sans avoir à modifier le client : c’est une approche par composants enfichables. Cette solution est plus élégante car la cohésion est forte et les risques de régression de l’ensemble faible en cas d’évolution applicative (cf. pattern Strategy du GOF).

2.5.3.2.2

Implémentation

Dans notre exemple, le client utilise un véhicule. Dans ce cas de figure, peu importe que l’implémentation soit une voiture, un avion ou un bateau. Il devient très simple alors d’ajouter de nouveaux comportements. Chaque implémentation prend en charge le comportement « start() ».

(13)

Figure 7 Exemple de polymorphisme

2.5.3.3

Pure Fabrication

2.5.3.3.1

Théorie

Le pattern Pure Fabrication ne représente pas le modèle du domaine : c’est une forme d’abstraction pour effectuer des traitements dans le seul but de rendre le système plus cohésif et de limiter le couplage. A contrario, le pattern Expert n’est pas un pattern Pure Fabrication car il représente un objet métier. Plus simplement ce sont des créations de l’esprit qui n’existent pas dans le modèle du domaine car ils ne représentent aucune donnée comme par exemple un client ou une facture.

2.5.3.3.2

Implémentation

La classe suivante cumule trop de responsabilités : elle est donc faiblement cohésive. En effet les notions de persistance exprimées par les méthodes loadCustomer() et saveCustomer() affaiblissent la cohésion de la classe.

Figure 8 Classe faiblement cohésive

Il est préférable dans ce cas d’utiliser, par exemple, la délégation de ces responsabilités à une autre classe.

(14)

2.5.3.4

Indirection

2.5.3.4.1

Théorie

L’indirection intervient lorsque l’on veut éviter un couplage entre deux Experts. Dans ce cas un objet servira d’intermédiaire. D’ailleurs beaucoup de Pure Fabrication sont crées pour des besoins d’indirection.

2.5.3.4.2

Implémentation

Prenons l’exemple de la classe Vector qui propose, pour énumérer ses éléments, une Enumeration (ancêtre de l’Iterator). Notre client souhaite manipuler cette liste mais sans vouloir de couplage avec cette Enumeration. Dans ce cas de figure il utilise un Adapter qui propose une interface Iterator pour le client, tout en manipulant (en interne) une Enumeration.

Figure 10 L'Adapter reduit le couplage

2.5.3.5

Law of Demeter

2.5.3.5.1

Théorie

Le pattern Law of Demeter (ou protection des variations) tente de limiter l’effort dû à l’évolution d’un système. Il consiste à utiliser uniquement des dépendances directes, c'est-à-dire des dépendances envers des composants qui rendent un service, et aucune dépendance indirecte, c'est-à-dire une dépendance envers un composant permettant d'obtenir la référence sur un composant rendant un service. Les parties jugées modulables ou susceptibles d’évoluer doivent être isolées.

2.5.3.5.2

Implémentation

Il existe plusieurs moyens à la disposition du concepteur pour rendre un système moins sensible aux variations :

 Externalisation de certaines valeurs dans des fichiers de propriétés.

 Une conception par interfaces (JDBC, JAXB, JNDI, …).

 Une communication par messages standards : l’émetteur et le récepteur ne se

connaissent pas (techniquement parlant).

 « Don’t talk to strangers » : éviter les dépendances non nécessaires avec des systèmes

sans en avoir réellement l’utilité.

D’ailleurs beaucoup de designs patterns et de principes de programmation vont dans ce sens. C’est le cas de l’encapsulation où les données et les traitements sont protégés de l’environnement

(15)

2.6

Design principles

2.6.1

Présentation

Les « design principles » sont 10 grands principes de conception répartis en 3 groupes :

 Gestion des évolutions et des dépendances entre classes.

 Organisation de l’application en modules.

 Gestion de la stabilité de l’application.

En appliquant ces principes, ont doit arriver à un programme robuste et facilement modifiable et ce sans y introduire d’erreurs.

2.6.2

Gestion des évolutions et des dépendances entre classes

2.6.2.1

Open/Closed principle

2.6.2.1.1

Théorie

Le principe d’ouverture/fermeture tente de limiter les impacts (et donc les coûts) liées aux changements. Tout module doit être ouvert aux extensions et fermé aux modifications. Le module peut donc être étendu pour proposer des comportements n’existant pas lors de sa conception mais les nouvelles extensions ne doivent pas altérer le code existant (Cf. pattern Strategy du GOF). Attention cependant à ne pas exagérer et ne pas mettre trop facilement des points d’ouverture dans votre application afin de ne pas injecter une complexité inutile. Il convient d’identifier les futurs points d’ouverture en s’inspirant des besoins pressentis par le développeur ou des changements multiples constatés durant le développement.

2.6.2.1.2

Implémentation

L’abstraction par utilisation d’interface est la solution la mieux adaptée en java pour limiter les impacts des nouvelles fonctionnalités.

Figure 11 Limitation de la variation logicielle par utilisation de l'héritage

Dans ce cas de figure si nous souhaitons ajouter un comportement (Boat), il suffit d’ajouter une classe dérivant de l’interface Vehicule. Les classes existantes ne sont pas modifiées et un comportement nouveau est supporté.

Ce motif s’applique naturellement si certains principes de bon sens sont respectés :

- Les variables sont privées : des variables publiques non justifiées (autre que final) sont plutôt

le signe d’une mauvaise conception.

- Les variables ne doivent pas être globales : elles maintiennent inutilement des références et

alourdissent le système.

L’utilisation du mot clé instanceof est à proscrire au niveau de l’abstraction. En effet si l’on souhaite ajouter un nouveau comportement ou une nouvelle fonctionnalité, il faudra modifier la section de code de l’abstraction (ou du client) qui adopte un comportement différent (modification d’une fonctionnalité existante) en fonction de l’implémentation.

(16)

2.6.2.2

Liskov substitution principle

2.6.2.2.1

Théorie

Le principe de substitution de Liskov (ou principe des 100%) part de l’hypothèse que le client doit pouvoir utiliser des objets dérivés sans s’en apercevoir et sans avoir à changer son comportement en fonction de l’implémentation. A tout moment une classe peut se substituer à une autre pour autant que ces deux classes implémentent la même interface. Ce principe est important car bon nombre d’héritages sont fait par commodité et sans réelle existence d’une nécessité d’abstraction. D’ailleurs, il prend à contre pied un autre mauvais principe (anti pattern dirons nous) qui veut que l’héritage serve à factoriser du code : les puristes lui préféreront la délégation. Dans le cas où une classe ne se substituerait pas parfaitement à une autre, il y a un problème de conception.

2.6.2.2.2

Implémentation

Dans cet exemple, chaque implémentation de Vehicule peut prendre la place d’une autre sans que le client puisse (et doive) s’en apercevoir.

Figure 12 Substitution possible entre les différentes implémentations

2.6.2.3

Dependency inversion principle

2.6.2.3.1

Théorie

Le principe d’inversion de dépendance part du précepte que la forte dépendance entre les couches d’une application, nuit à sa réutilisation et à son évolution. Par exemple une couche IHM doit connaître la couche métier pour pouvoir l’utiliser. La couche IHM est écrite pour cette couche métier et ne peut être réutilisée autrement. Pour éviter ce couplage, les modules de haut niveau ne doivent pas connaître les modules de bas niveau. Ils doivent plutôt dépendre d’abstractions. Le module de haut niveau s’inscrit au module de bas niveau et écoute ses évènements (on retrouve ici les fondements de l’injection de code). Cette approche permet aussi de faire apparaître clairement les messages entre les objets et donc de facilement les normaliser.

(17)

L’utilisation d’une abstraction et d’une inversion des dépendances réduit le couplage et facilite la réutilisation. La couche métier OrderBusiness (ou modèle) ne connaît que l’interface OrderInterface (implémenté par OrderAction) vers laquelle elle envoie les évènements systèmes.

Figure 14 Réutilisation plus aisé

Aujourd’hui des containers légers comme Spring ou HiveMind offre une approche similaire. Les services sont définis par une interface et une implémentation. Le container instancie l’implémentation correspondante. Cette approche est intéressante en plusieurs points :

 Couplage faible : seul les interfaces sont connus.

 Principe d’ouverture/fermeture respecté.

 Globalement l’application est moins sensible à la variation.

2.6.2.4

Interface segregation principle

2.6.2.4.1

Théorie

Il arrive que certaines classes remplissent plusieurs rôles au sein d’un système. Ce type de classe expose tous ses services à chaque client et ne facilite pas la compréhension. De plus chaque modification de la classe impacte potentiellement tous les clients. Ce pattern propose donc de limiter la taille de la classe en répartissant les méthodes dans plusieurs classes (regroupement technique ou fonctionnel par exemple).

2.6.2.4.2

Implémentation

Lorsqu’un service propose trop de fonctionnalités…

Figure 15 Trop de fonctionnalités proposées…

(18)

Figure 16 Répartition plus homogène

2.6.3

Organisation de l’application en modules

2.6.3.1

Reuse/Release equivalence principle

2.6.3.1.1

Théorie

Le pattern d’équivalence livraison/réutilisation propose d’avoir une approche modulaire de l’application. Chaque sous-système est considéré comme une application (ou un Framework), et développé et maintenu par un tiers. Il doit être utilisé tels quel, sans avoir à en comprendre les mécanismes internes.

2.6.3.1.2

Implémentation

Le monde java avec les spécifications du JCP (Java Community Process) ainsi que les différents Frameworks existants se prêtent bien à cette approche (Hibernate, Spring, Struts, etc…).

Figure 17 Utilisation du pattern DAO

Plutôt que de réécrire un Framework de persistance avec un pattern type DAO, utilisez un outil d’ORM (Object Relational Mapping).

(19)

Figure 18 Utilisation d'un ORM

L’approche la plus professionnelle est d’utiliser les 2 concepts : une DAO pour masquer l’implémentation (Hibernate par exemple) et l’utilisation d’une session Hibernate dans une implémentation dédié (HibernateDao). Une factory prend la responsabilité de la création de la bonne implémentation (décrite dans un fichier de propriétés par exemple).

Figure 19 Utilisation conjointe : ORM + DAO

2.6.3.2

Common reuse principle

2.6.3.2.1

Théorie

Le pattern de réutilisation commune propose de rassembler dans le même package, les classes susceptibles d’être réutilisées conjointement. Il est important de rassembler ensemble des classes ayant des rapports de dépendances les unes avec les autres, inutile en effet de mettre ensemble des classes réutilisables mais ne couvrant pas le même domaine technique ou fonctionnel.

2.6.3.2.2

Implémentation

Imaginons un système de persistance de type DAO. Il sera nécessaire de posséder des classes d’outils dédiées, par exemple, à la gestion de la connexion, de la transaction ou du cache. Toutes ces classes devront être localisées dans le même package et distribuées ensemble.

2.6.3.3

Common closure principle

2.6.3.3.1

Théorie

Le principe de fermeture commune stipule que les classes qui seront impactées par un même changement soient regroupées dans un même package.

(20)

2.6.4

Gestion de la stabilité de l’application

2.6.4.1

Acyclic dependencies principle

2.6.4.1.1

Théorie

Le principe de dépendances acycliques repose sur la répartition des classes en packages pour limiter la propagation des changements au sein de l’application. La propagation des changements étant guidée par les dépendances entre packages, l’organisation de ces dépendances est un élément fondamental de l’architecture. Pour pourvoir gérer ces changements, il convient de ne pas introduire de dépendance cyclique dans les packages et de regrouper les classes dépendantes dans le même package.

2.6.4.1.2

Implémentation

Dans ce cas de figure les dépendances sont cycliques, ce qui est un signe de mauvaise conception.

Figure 20 Dépendance cyclique

Pour contourner un besoin de référence cyclique, utilisez le principe d’inversion des dépendances.

2.6.4.2

Stable dependencies principle

2.6.4.2.1

Théorie

Le principe de relation de dépendance stable stipule qu’un package doit dépendre d’un package plus stable que lui. Globalement ce principe permet de stabiliser l’application. Le développeur apportera donc encore plus de soin à un module si celui ci est fortement utilisé par les autres. Plusieurs critères permettent de définir le niveau de stabilité d’un module :

 Plus les dépendances de ce module vers d’autres modules sont élevées, plus il est

susceptible d’être impacté par un changement et donc moins il est considéré comme stable.

 Plus il existe des relations vers ce module et plus il est considéré comme stable.

2.6.4.2.2

Implémentation

Dans ce cas de figure, on admet que la stabilité du composant doit être et sera (par force) maximale.

(21)

Figure 21 Stabilité maximale

Par contre, on admet ici que la stabilité du composant est minimale puisque chaque changement d’un des composants peut l’affecter ou le déstabiliser.

Figure 22 Stabilité minimale

2.6.4.3

Stable abstractions principle

2.6.4.3.1

Théorie

Le principe de stabilité des abstractions énonce que les packages les plus abstraits doivent être les plus stables et les plus concrets les moins stables. Le degré de stabilité d’un package doit correspondre à son degré d’abstraction.

2.6.4.3.2

Implémentation

Dans une application, les règles métiers seront plus stables que les objets techniques qu’ils utilisent (EJB, Log, etc.) : En effet ceux-ci étant plus concrets, ils sont plus sensibles aux évolutions.

2.6.5

Conclusion

Les principes énoncés ci-dessus doivent vous permettre d’obtenir une application extensible, robuste et réutilisable. Contrairement aux design patterns, ce ne sont que des principes et non une recette directement applicable sur du code. Elle on toutefois l’intérêt de définir un cadre et fixant de fait certaines limites.

(22)

2.7

Gang Of Four [GOF]

2.7.1

Présentation

Le GOF propose 23 patterns avec une classification en 3 catégories :

 Création : propose des modèles pour faciliter ou rationaliser la création de classes. Ils

permettent de faire la différence entre la création et le montage des objets et plus globalement d’abstraire le mécanisme d’instanciation.

 Structure : Ces patterns proposent aussi bien des modèles d’assemblages que

d’adaptation. Ce sont des patterns adaptés aux problèmes courants (structures de données, …).

 Comportement : propose des modèles qui réagissent à leur environnement en adoptant

des comportements spécifiques différents au cours de leur cycle de vie.

Note : Ce paragraphe n’a pas pour vocation de proposer un cours complet sur les Design patterns du GOF : pour de plus amples renseignements, se reporter au livre de référence « Design Patterns Elements of Reusable Object-Oriented Software ».

Chaque motif est noté de 1 à 5 en fonction de son intérêt.

2.7.2

Patterns de création

2.7.2.1

Abstract Factory 































2.7.2.1.1

Théorie

2.7.2.1.1.1 Présentation

Une fabrique abstraite délocalise la responsabilité de la création d’un objet. Plutôt que de confier cette responsabilité au constructeur de l’objet, un objet dédié (la fabrique) la prend en charge. On peut alors facilement utiliser des fabriques différentes pour le même type d’objet. Elle fournit donc une interface pour créer des objets apparentés sans avoir la nécessité de spécifier leur classe concrète.

2.7.2.1.1.2 UML

Figure 23 UML Théorique Abstract Factory

2.7.2.1.2

Implémentation

2.7.2.1.2.1 Présentation

(23)

2.7.2.1.2.2 UML

Figure 24 UML implémentation Abstract Factory

2.7.2.1.2.3 Utilisation

Factory pFactory = new PlaneFactory();

Vehicule vehicule = pFactory.build();

//code...

2.7.2.2

Builder 































2.7.2.2.1

Théorie

2.7.2.2.1.1 Présentation

Bien que souvent confondu avec le pattern Abstract Factory, le Builder est une approche différente de la construction d’objets. En effet, outre le fait qu’il construit l’objet, il définit la façon de le construire (on peut presque parler de configuration). On l’utilise aussi dans le cas où l’algorithme de création et la méthode d’assemblage doivent être découplés de l’objet.

2.7.2.2.1.2 UML

Figure 25 UML théorique Builder

Correspondance diagramme théorique implémentation AbstractFactory Factory ConcreteFactory TruckFactory, PlaneFactory AbstractProduct Vehicule

(24)

2.7.2.2.2

Implémentation

2.7.2.2.2.1 Présentation

Ici le Director prend la responsabilité de l’ordre de montage de l’A380, alors que l’A380Builder conserve la responsabilité de la construction des différentes parties.

2.7.2.2.2.2 UML

Figure 26 UML implémentation Builder

2.7.2.2.2.3 Utilisation

A380Builder builder = new A380Builder(); new Director(builder).build();

Plane product = builder.getResult();

2.7.2.3

Factory Method 































2.7.2.3.1

Théorie

2.7.2.3.1.1 Présentation

La Factory Method est une variante de l’Abstract Factory. En effet, plutôt que de laisser au choix du client l’instanciation de la classe concrète de construction, c’est la classe concrète qui en prend la responsabilité. Correspondance diagramme théorique implémentation Product Plane ConcreteBuilderProduct A380 ConcreteBuilder A380Builder Builder PlaneBuilder Director Director

(25)

2.7.2.3.1.2 UML

Figure 27 UML théorique Factory Method

2.7.2.3.2

Implémentation

2.7.2.3.2.1 Présentation

Contrairement à l’Abstract Factory, c’est le client (AbstractCreator) qui prend la responsabilité de la création de l’objet via sa méthode factoryMethod().

2.7.2.3.2.2 UML

Figure 28 UML implémentation Factory Method

2.7.2.3.2.3 Utilisation Cf. méthode sampleUse(). Correspondance diagramme théorique implémentation AbstractProduct Product ConcreteProduct Truck AbstractCreator AbstractCreator ConcreteCreator TruckCreator

(26)

2.7.2.4

Prototype 































2.7.2.4.1

Théorie

2.7.2.4.1.1 Présentation

Le prototype offre une autre approche quant à la création d’objet. Le principe de Factory est intéressant, mais pèche par la création d’autant de Factory concrètes qu’il existe d’objets concrets. Le pattern Prototype donne à l’objet concret la responsabilité de sa fabrication par une méthode de clonage.

2.7.2.4.1.2 UML

Figure 29 UML théorique Prototype

2.7.2.4.2

Implémentation

2.7.2.4.2.1 Présentation

Dans notre exemple, la classe Plane et Truck ont la capacité, via la méthode clone(), de se dupliquer. On remarque une forte similitude avec java qui propose, via l’objet « Object », l’interface clone().

2.7.2.4.2.2 UML

Figure 30 UML implémentation Prototype

Correspondance diagramme

théorique implémentation

Prototype Prototype

(27)

//code...

2.7.2.5

Singleton 































2.7.2.5.1

Théorie

2.7.2.5.1.1 Présentation

Il est parfois nécessaire de n’avoir qu’une seule instance d’une classe dans un système : c’est le cas lorsqu’un seul objet est nécessaire pour coordonner un ensemble. Le pattern Singleton propose un motif pour contrôler la création de l’instance. Cette responsabilité est attribuée à l’objet concerné pour éviter un phénomène d’instanciation multiple. A noter que le pattern singleton n’est pas nécessairement limitatif à une seule instance : on peut parfaitement l’utiliser pour en contrôler plusieurs comme un pool d’objet.

2.7.2.5.1.2 UML

Figure 31 UML théorique Singleton

2.7.2.5.2

Implémentation

2.7.2.5.2.1 Présentation

Dans cet exemple il suffit d’utiliser la méthode statique getInstance() pour obtenir une poignée sur l’objet unique. L’instance retournée sera systématiquement la même.

2.7.2.5.2.2 UML

Figure 32 UML implémentation Singleton

2.7.2.5.2.3 Utilisation

Singleton single = Singleton.getInstance();

Correspondance diagramme

théorique implémentation

(28)

2.7.3

Patterns de structure

2.7.3.1

Adapter 































2.7.3.1.1

Théorie

2.7.3.1.1.1 Présentation

Le pattern Adapter permet de faire fonctionner un objet avec une interface qu’il n’implémente pas habituellement. Ce motif est particulièrement pratique si vous ne souhaitez pas dépendre d’une implémentation en particulier ou si vous souhaitez l'accommoder. L’adapter fera le lien entre les méthodes de la nouvelle interface vers l’ancienne.

2.7.3.1.1.2 UML

Figure 33 UML théorique Factory Adapter

2.7.3.1.2

Implémentation

2.7.3.1.2.1 Présentation

La version proposée est à base de délégation puisque java ne supporte pas l’héritage multiple. Dans cet exemple, nous adaptons un objet de type Enumeration pour qu’il supporte l’interface Iterator. L’utilisation pour le client est transparente puisqu’il utilisera une Enumeration comme un Iterator.

2.7.3.1.2.2 UML

(29)

2.7.3.1.2.3 Utilisation

StringTokenizer tokenize = new StringTokenizer("java c'est bien");

for (Iterator iter = new IteratorEnumerationAdapter(tokenize); iter.hasNext();) {

String element = (String) iter.next(); System.out.println(element); }

2.7.3.2

Bridge 































2.7.3.2.1

Théorie

2.7.3.2.1.1 Présentation

Le bridge permet de découpler une abstraction de son implémentation pour les rendre indépendantes des évolutions. On évite un lien permanent entre l’abstraction et l’implémentation, ce qui permet à chacun d’évoluer indépendamment. Au final les modifications d’implémentations n’entraînent pas de recompilation de l’abstraction.

2.7.3.2.1.2 UML

Figure 35 UML théorique Bridge

2.7.3.2.2

Implémentation

2.7.3.2.2.1 Présentation

Dans notre exemple nous déléguons la gestion des données d’un groupe de client à un Implementor. L’abstraction du groupe de client propose des méthodes pour naviguer d’un client à un autre. Enfin pour notre exemple nous utilisons une gestion des données en mémoire. Au passage on s’aperçoit qu’il est très facile de proposer une autre implémentation (FileData ou DBData par exemple) sans qu’il soit nécessaire de recompiler l’abstraction.

Correspondance diagramme

théorique implémentation

Target Iterator

Adapter IteratorEnumerationAdapter

(30)

2.7.3.2.2.2 UML

Figure 36 UML implémentation Bridge

2.7.3.2.2.3 Utilisation

AbstractCustomers ab = new Customers("bons clients");

ab.setImplementor(new InMemoryData());

ab.add("claude durillon");

2.7.3.3

Composite 































2.7.3.3.1

Théorie

2.7.3.3.1.1 Présentation

Ce pattern s’adapte parfaitement à l’utilisation et à la manipulation d’objets complexes (structure par association, arborescence, …) dont la profondeur n’est pas connue. Il propose une interface simplifiée pour la manipulation d’objets uniques ou d’objets composés du même type.

Correspondance diagramme théorique implémentation Abstraction AbstractCustomer RefinedAbstraction Customers Implementor DataObject ConcreteImplementor InMemoryData

(31)

2.7.3.3.1.2 UML

Figure 37 UML théorique Composite

2.7.3.3.2

Implémentation

2.7.3.3.2.1 Présentation

Dans notre exemple, la classe Product contient une liste de ses enfants eux-mêmes de type Product. Cette arborescence représente la notion de « est composé de… ». La manipulation de ce graphe complexe se fait depuis le ProductComposite pour simplifier les opérations.

2.7.3.3.2.2 UML

Figure 38 UML implémentation Composite

2.7.3.3.2.3 Utilisation

Product pizza = new Product("1245789456124", "pizza 4 fromages"); Product pate = new Product("1245789446124", "pate à pizza");

Correspondance diagramme

théorique implémentation

Component, Leaf Product

(32)

Product farine = new Product("1245789446127", "farine");

Product eau = new Product("1245781446127", "eau");

Product fromage = new Product("1745789446127", "fromage");

//crée la pizza

ProductComposite composite = new ProductComposite(pizza);

//ajoute la pâte

composite.addProduct(pate, pizza.getId());

//la pâte est constituée d'eau et de farine

composite.addProduct(farine, pate.getId()); composite.addProduct(eau, pate.getId());

//enfin on ajoute le fromage

composite.addProduct(fromage, pizza.getId());

//en fait nan pas de fromage : trop gras

composite.removeProduct(fromage.getId());

2.7.3.4

Decorator 































2.7.3.4.1

Théorie

2.7.3.4.1.1 Présentation

Le motif Decorator permet d’accrocher dynamiquement des responsabilités à un objet. Il remplace le principe d’héritage en offrant une méthode plus souple. On peut aussi l’utiliser si la classe ne peut être héritée : c’est le cas si la classe utilise le modificateur final.

2.7.3.4.1.2 UML

Figure 39 UML théorique Decorator

2.7.3.4.2

Implémentation

2.7.3.4.2.1 Description

Dans notre exemple, nous allons ajouter une responsabilité sur la classe LibraryItem : les instances de cette classe auront la capacité d’être empruntées par des clients. Par le truchement de l’héritage, ses classes filles, en bénéficieront aussi. La méthode display() est surchargée par la classe Borrowable pour afficher les informations des emprunteurs en cours : c’est dans cette méthode que les nouvelles responsabilités sont ajoutées.

(33)

2.7.3.4.2.2 UML

Figure 40 UML implémentation Decorator

2.7.3.4.2.3 Utilisation

Book book = new Book("Michael MARSHALL", "Le sang des anges", 10);

book.display();

Video video = new Video("Jacques TATI", "Mon oncle", 200 ,5); video.display();

Borrowable borrowVideo = new Borrowable(video);

borrowVideo.borrowItem("Mr Dupont"); borrowVideo.borrowItem("Mr Durand");

//invocation des responsabilités supplémentaires

borrowVideo.display();

2.7.3.5

Facade 































2.7.3.5.1

Théorie

2.7.3.5.1.1 Présentation

Le pattern Facade fournit un point d’entrée unique et une interface simple à un système complexe. Il s’inscrit dans un principe de découpage en couches et le risque d’un couplage fort avec les classes internes d’une API est réduit. On peut le comparer à un tableau de bord d’un véhicule : vous ne voyez et ne pouvez modifier que ce qui est nécessaire (essuie-glace, clignotants) et le fait qu’il existe des pistons et des bielles dans le moteur ne vous concerne pas.

Correspondance diagramme

théorique implémentation

Component LibraryItem

ConcreteComponent Book, Video

Decorator Decorator

(34)

2.7.3.5.1.2 UML

Figure 41 UML théorique Facade

2.7.3.5.2

Implémentation

2.7.3.5.2.1 Description

Dans cet exemple, notre façade est matérialisée par la classe Compiler. Elle est le point d’entrée unique à notre système de compilation.

2.7.3.5.2.2 UML

Figure 42 UML implémentation Facade

2.7.3.5.2.3 Utilisation

5 Compiler compiler = new Compiler();

6 compiler.compile();

2.7.3.6

FlyWeight 































2.7.3.6.1

Théorie

2.7.3.6.1.1 Présentation

Le motif FlyWeight propose une technique de partage permettant la mise en œuvre d’une grande quantité d’objet de granularité fine. Ce pattern s’applique particulièrement bien si l’état des objets peut être externalisé, si le stockage des éléments coûte cher et enfin si les objets n’ont pas d’identifiant (c’est le type de la classe qui identifie l’objet et non un de ses attributs). Le Pattern FlyWeight propose le partage d’objet mais ne l’impose pas.

Correspondance diagramme

théorique implémentation

Facade Compiler

Subsystem CodeGenerator, Scanner,

(35)

2.7.3.6.1.2 UML

Figure 43 UML théorique FlyWeight

2.7.3.6.2

Implémentation

2.7.3.6.2.1 Présentation

Prenons un éditeur de texte pour notre exemple. Si nous raisonnons « pure objet », nous pensons de la manière suivante : pour chaque caractère de chaque ligne et de chaque page, j’ai un objet de type caractère. L’idée peut être intéressante mais on se rend vite compte que le nombre des objets peut rapidement « exploser » pour des documents de taille importante. Le motif FlyWeight permet d’externaliser certaines données : par exemple la largeur et la hauteur en point, le code du caractère ne varient jamais. Par contre remis dans le contexte de la page, il est nécessaire de connaître en plus la position ligne/colonne, la police employée, etc… Ce pattern fait donc le distinguo entre les données intrinsèques (Position ligne/colonne, etc.) et externalisables (Code caractère, etc.).

(36)

Figure 44 UML implémentation FlyWeight

2.7.3.6.2.3 Utilisation

GraphicalItemFactory factory = new GraphicalItemFactory();

/*String line1 = "my flowers are beautiful"; String line2 = "my taylor is rich";*/

String line1 = "ABBBZZZABBZ"; String line2 = "ABABABAAAAABBZ";

Row row1 = (Row) factory.getRow(line1); Row row2 = (Row) factory.getRow(line2);

//crée le context

PageContext context = new PageContext();

//positionne la fonte sur la 1ere ligne pour le caractère de 3 à 10

context.setFont("Arial", 1, 3 , 10);

//parcours la 1ere ligne for (char c : row1.charArray()) {

GraphicalItem character = factory.getCharacter(c); character.display(context); context.next(); } Correspondance diagramme théorique implémentation Flyweight GraphicalItem

ConcreteFlyweight Character, CharacterA, CharacterB,

CharacterZ Unshared

ConcreteFlyweight

Row

FlyweightContext Context, PageContext

(37)

context.next(); }

2.7.3.7

Proxy 































2.7.3.7.1

Théorie

2.7.3.7.1.1 Présentation

Le Proxy a pour fonction de se positionner entre un objet et son client. Il permet de référencer un objet autrement qu’avec une poignée. Plusieurs Proxy existent en fonction de leur finalité technique. On utilisera un Remote Proxy pour cacher toute la couche d’instanciation distante, un Protection Proxy pour gérer l’authentification, un Lazy Proxy pour gérer l’instanciation d’un objet à la demande, etc… Attention à ne pas le confondre avec une Facade ou un Adapter.

2.7.3.7.1.2 UML

Figure 45 UML théorique Proxy

2.7.3.7.2

Implémentation

2.7.3.7.2.1 Présentation

Dans notre exemple, nous utilisons un Proxy pour manipuler un objet image. Le principal problème est que l’image peut ne pas avoir terminé son chargement et les méthodes appelées risquent de provoquer une exception. Le Proxy va donc mettre en attente les appels de méthodes, tant que l’image n’est pas chargée. Une fois celle ci téléchargée, les méthodes s’exécutent. L’utilisateur du Proxy subira un temps de latence lors du premier appel.

2.7.3.7.2.2 UML

(38)

2.7.3.7.2.3 Utilisation

//normalement le client ne doit jamais écrire ce qui suit //c'est plutôt le rôle d'une factory

ImageProxy proxy = new ImageProxy(new Image());

proxy.draw();

2.7.4

Patterns de comportement

2.7.4.1

Chain of Responsability 































2.7.4.1.1

Théorie

2.7.4.1.1.1 Présentation

Ce pattern propose de découpler l’émetteur d’une requête par rapport aux récepteurs potentiels. La requête traverse la chaîne des récepteurs jusqu'à ce qu’un objet la traite. Le couplage est fortement réduit et l’attribution des responsabilités est plus souple car des récepteurs peuvent être créés ou retirés dynamiquement. Il reste un risque cependant qu’aucun des récepteurs ne réponde à la requête.

2.7.4.1.1.2 UML

Figure 47 UML théorique Chain of Responsability

2.7.4.1.2

Implémentation

2.7.4.1.2.1 Présentation

Dans cet exemple, des employés vont prendre des décisions sur un projet. Chaque participant inscrit va recevoir le projet jusqu'à ce qu’il soit traité. On peut noter que la gestion de la liste (permutation, suppression, etc...) n’est pas aisée.

Correspondance diagramme

théorique implémentation

Subject Graphic

Proxy ImageProxy

(39)

2.7.4.1.2.2 UML

Figure 48 UML implémentation Chain of Responsability

2.7.4.1.2.3 Utilisation

Employee director = new Employee("Director");

Employee vicePresident = new Employee("vicePresident");

Employee president = new Employee("president");

director.setSuccessor(vicePresident); vicePresident.setSuccessor(president);

//on peut faire démarrer la requête depuis n'importe quel employé

Project project = new Project(1000, "Projet qui tue");

director.handleRequest(project);

2.7.4.2

Command 































2.7.4.2.1

Théorie

2.7.4.2.1.1 Présentation

Propose de supprimer le couplage entre l’objet qui invoque une opération et celui qui la réalise. Il fonctionne comme le motif Bridge à la différence près qu’il s’occupe des traitements et non des données. Au final, il permet de manipuler les traitements comme des objets et de pouvoir les défaire à la demande.

Correspondance diagramme

théorique implémentation

Handler Approver

(40)

2.7.4.2.1.2 UML

Figure 49 UML théorique Command

2.7.4.2.2

Implémentation

2.7.4.2.2.1 Présentation

Dans notre exemple, la classe User est le point d’entrée du service : elle enregistre les commandes et permet de les défaire sur plusieurs niveaux. La classe CalculatorCommand a la responsabilité pour faire et défaire une commande à l’aide du Calculator.

2.7.4.2.2.2 UML

Figure 50 UML implémentation Command

2.7.4.2.2.3 Utilisation

User user = new User();

user.compute('+', 100); Correspondance diagramme théorique implémentation Invoker User Command Command ConcreteCommand CalculatorCommand Receiver Calculator

(41)

user.undo(4); // refait 3 commandes user.redo(3);

2.7.4.3

Interpreter 































2.7.4.3.1

Théorie

2.7.4.3.1.1 Présentation

Définit une représentation pour une grammaire ainsi qu’un interpréteur pour cette grammaire. Il facilite la création, la modification et l’extension de cette grammaire. Pratique pour une grammaire simple, ce motif peut rapidement devenir complexe à maintenir à cause du nombre élevé de classes. Enfin, les performances générales de ce pattern sont souvent médiocres.

2.7.4.3.1.2 UML

Figure 51 UML théorique Interpreter

2.7.4.3.2

Implémentation

2.7.4.3.2.1 Présentation

L’exemple suivant propose un interpréteur pour des codes romains.

2.7.4.3.2.2 UML

(42)

2.7.4.3.2.3 Utilisation

String roman = "MCMXXVIII"; Context context = new Context(roman);

ArrayList<Expression> tree = new ArrayList<Expression>();

tree.add(new ThousandExpression());

tree.add(new HundredExpression());

tree.add(new TenExpression());

tree.add(new OneExpression());

for (Expression expression : tree) {

expression.interpret(context); } System.out.println(roman + "=" + context.getOutput());

2.7.4.4

Iterator 































2.7.4.4.1

Théorie

2.7.4.4.1.1 Présentation

Propose un accès séquentiel aux éléments d’un objet sans en exposer la structure interne. Ce pattern est bien connu en java puisqu’il permet d’exposer le contenu d’une collection via la méthode iterator(). Il offre une interface uniforme pour parcourir différentes structures.

2.7.4.4.1.2 UML

Figure 53 UML théorique Iterator

2.7.4.4.2

Implémentation

2.7.4.4.2.1 Présentation

Dans l’exemple suivant nous itérons une collection.

2.7.4.4.2.2 UML Correspondance diagramme théorique implémentation Context Context AbstractExpression Expression TerminalExpression ThousandExpression, HundredExpression, TenExpression, OneExpression NonTerminalExpression

(43)

Figure 54 UML implémentation Iterator

2.7.4.4.2.3 Utilisation

//nous évitons d'utiliser java.util.Collection pour l'exemple

String[] strings = {"a","z","e","r","t","y"}; Collection collection = new Collection(strings);

//vide le contenu dans la sortie standard

for (Iterator iter = collection.iterator(); iter.hasNext();) {

String element = (String) iter.next(); System.out.println(element); }

2.7.4.5

Mediator 































2.7.4.5.1

Théorie

2.7.4.5.1.1 Présentation

Le Mediator réduit le couplage entre plusieurs classes en les dispensant de se faire explicitement référence : il prend en charge la manière dont les objets interagissent et de cette façon il formalise la coopération entre objets.

2.7.4.5.1.2 UML

Figure 55 UML théorique Mediator

Correspondance diagramme théorique implémentation Iterator Iterator ConcreteIterator RealIterator Agregate AbstractCollection ConcreteAgregate Collection

(44)

2.7.4.5.2

Implémentation

2.7.4.5.2.1 Présentation

Dans notre exemple, nous utilisons un chat room pour le jeu Counter Strike. Chaque participant envoie des messages aux personnes enregistrées.

2.7.4.5.2.2 UML

Figure 56 UML implémentation Mediator

2.7.4.5.2.3 Utilisation

CounterStrikeChatroom chatRoom = new CounterStrikeChatroom();

Participant killer = new Fighter("killer");

Participant grenadier = new Fighter("grenadier");

Participant mechant = new Fighter("mechant");

chatRoom.register(killer); chatRoom.register(grenadier); chatRoom.register(mechant); killer.send("yahooooo"); grenadier.send("gogogo"); mechant.send("no prisonner");

2.7.4.6

Memento 































2.7.4.6.1

Théorie

2.7.4.6.1.1 Présentation

Ce pattern propose de capturer et restaurer au besoin, l’état interne d’un objet et ce sans violer le principe d’encapsulation. La sauvegarde et la restauration peuvent s’effectuer sur une partie ou l’ensemble de l’objet. Correspondance diagramme théorique implémentation Mediator AbstractChatroom Colleague Participant ConcreteMediator CounterStrikeChatRoom ConcreateColleague Fighter

(45)

2.7.4.6.1.2 UML

Figure 57 UML théorique Memento

2.7.4.6.2

Implémentation

2.7.4.6.2.1 Présentation

Dans notre exemple, le Memento sauvegarde l’état du véhicule avec les options.

2.7.4.6.2.2 UML

Figure 58 UML implémentation Memento

2.7.4.6.2.3 Utilisation

//configuration du véhicule

Car car = new Car("Renault megane");

car.setAirConditionning(true); car.setSlidingRoof(true);

//sauvegarde de la configuration

CarMemory mem = new CarMemory();

mem.setMemento(car.createMemento());

//continue à modifier

car.setAirConditionning(false);

//restore l'état de l'objet

car.restoreMemento(mem.getMemento());

2.7.4.7

Observer 































2.7.4.7.1

Théorie

2.7.4.7.1.1 Présentation

Définit une dépendance dynamique de type 1 à plusieurs, de façon telle que, quand un objet change d’état, tous ceux qui en dépendent en soient notifiés. Il permet d’isoler la relation du Sujet

Correspondance diagramme

théorique implémentation

Originator Car

Memento CarMemento

(46)

vers les observateurs : le sujet ne les connaît pas mais il peut les prévenir. Cependant il devient difficile de mesurer les interdépendances, voire les références circulaires.

2.7.4.7.1.2 UML

Figure 59 UML théorique Observer

2.7.4.7.2

Implémentation

2.7.4.7.2.1 Présentation

Dans notre exemple, 2 investisseurs écoutent le cours de l’action IBM. Chaque investisseur s’inscrit par le biais de la méthode attach(). A noter que Java propose un système équivalent basé sur java.util.Observer et java.util.Observable.

2.7.4.7.2.2 UML

Figure 60 UML implémentation Observer

2.7.4.7.2.3 Utilisation Correspondance diagramme théorique implémentation Subject Stock ConcreteSubject IBM Observer Investor

Figure

Figure 1 le Customer est couplé avec Invoice mais aussi avec le Printer
Figure 4 Attention à une trop forte cohésion !!
Figure 6 Limitation du couplage par utilisation d’un Controller
Figure 10 L'Adapter reduit le couplage
+7

Références

Documents relatifs

a) La qualité de la relation d'attachement joue un rôle modérateur sur l'association entre les pratiques parentales et les troubles du comportement:

Ce master a une maquette pédagogique conçue spécialement pour la distance ; il fonctionne avec des étudiants rompus au numérique et des formateurs spécialistes des TICE ;

Résumé Cet article présente un nouveau protocole d’apprentissage de la programmation dans lequel les séquences de travail sont structurées en cycles : d’abord

Dans de nombreux pays, l’universitarisa- tion de la formation des enseignant.e.s a été motivée par le besoin de nourrir ce métier de savoirs issus des sciences de l’éducation,

mentionner l’équipe porteuse (avec les noms, prénoms mais également les disciplines enseignées et les établissements d’origine), le parcours éducatif auquel il

La figure 1 illustre la répartition des personnels enseignants de l’enseignement supérieur par statuts, telle que décrite par ce document, dans lequel on peut également lire que

L'étude des thèmes et des motifs exploités lors de l'entrée solennelle de Charles Quint à Bologne en 1529 et de la procession solennelle après son couronnement en 1530

activities, many participants describe notable nuances in their interpretation of various patient behaviors. The latter refer to patient receptivity to care, delays in consulting,