Après toutes ces émotions, vous allez enfin avoir une solution à ce problème de modification de code source !
Si vous vous souvenez de ce que j'ai dit dans l'introduction, un des fondements de la programmation orientée objet est :
l'encapsulation !
Le pattern strategy est basé sur ce principe simple.
L'encapsulation est un mécanisme visant à rassembler des données et / ou des méthodes au sein d'une structure spécifique.
Ces méthodes et / ou ces données sont ainsi réutilisables partout ailleurs dans le programme.
Je me doute que cette phrase semble pompeuse... Mais remplacez "
une structure" par "
un objet".
Vous devez mieux comprendre le sens de cette phrase, non ?
Bon, vous avez compris que le pattern strategy consiste à créer des objets avec des données et / ou méthodes.
Oui, on comprend bien, mais lesquelles ?
Tout simplement ce qui change dans votre programme !
Le principe de base de ce pattern est le suivant :
isolez ce qui varie dans votre programme et encapsulez-le !
Désolé, mais on ne comprend toujours pas...
Pas de panique, nous allons y aller doucement.
Déjà, quels sont les éléments qui ne cessent de varier dans notre programme ?
- La méthode combattre() ;
- la méthode seDeplacer() ;
- la méthode soigner().
Nous avons tenté, en vain, de déployer ces comportements dans notre hiérarchie de classe, mais sans grand succès dû aux problèmes cités plus haut...
Ce qui serait vraiment grandiose, ce serait d'avoir la possibilité de ne modifier que les comportements et non les objets qui ont ces comportements !
Là, je vous arrête un moment. Vous venez de fournir la solution de vive voix. Vous avez dit : "
Ce qui serait vraiment grandiose, ce serait d'avoir la possibilité de ne modifier que les comportements et non les objets qui ont ces comportements".
Lorsque je vous ai présenté les diagrammes UML, je vous ai fourni une astuce pour bien différencier les liens entre les objets. Dans notre cas, nos classes héritant de
Personnage héritent aussi des ses comportements et, par conséquent, on peut dire que nos classes filles sont des
Personnage.
Concernant les comportements de la classe mère, ils semblent ne pas être au bon endroit dans la hiérarchie. Vous ne savez plus quoi en faire et vous vous demandez s'ils ont vraiment leur place dans cette classe ?
Il vous suffit de sortir ces comportements de la classe mère, de créer une classe abstraite ou une interface symbolisant ce comportement, et de dire à votre classe
Personnage d'
avoir ces comportements.
Voici mon nouveau diagramme de classes :
Ouh là ! Qu'est-ce que c'est que toutes ces interfaces ?
Je me doutais un peu que vous fronceriez les sourcils...
N'oubliez pas que votre code doit être souple et robuste et, même si ce chapitre vous montre les limites de l'héritage, n'oubliez pas que le polymorphisme est inhérent à l'héritage (et aux implémentations d'interfaces).
Il faut que vous vous rendiez compte qu'utiliser une interface de cette manière revient à créer un super-type de variable et, du coup, nous pourrons utiliser les classes héritant de ces interfaces de façon polymorphe, sans se soucier de savoir de quelle classe nos objets sont issus !
Dans notre cas, nous allons avoir des objets de type
EspritCombatif,
Soin et
Deplacement dans notre classe
Personnage !
Avant de nous lancer dans le codage de nos nouvelles classes, vous devez vous rendre compte que leur nombre a considérablement augmenté depuis le début de ce chapitre.
Afin de pouvoir y voir plus clair et ainsi gagner en clarté, nous allons gérer nos différentes classes avec différents
packages.
Comme je vous l'avais déjà dit dans un précédent chapitre, un package est un dossier comprenant plusieurs classes ou dossiers en son sein. Les classes sont regroupées par utilité ou par thème.
L'un des avantages de faire ceci est que nous allons gagner en lisibilité dans notre package par défaut mais aussi que les classes mises dans un package sont plus facilement transportables d'une application à l'autre. Pour cela, il vous suffira d'inclure le dossier de votre package dans un projet et d'importer les classes qui vous intéressent !
Comment crée-t-on un nouveau package ?
Ah, ce n'est pas difficile du tout, il vous suffit de cliquer sur cette icône :
Une boîte va s'ouvrir vous demandant le nom de votre package :
Attention : il existe aussi une convention de nommage pour les packages !
- Ceux-ci doivent être écrits entièrement en minuscules.
- Les caractères doivent être de a à z, de 0 à 9 et un point (.).
- Sun indique que tout package doit commencer par : com, edu, gov, mil, net, org ou les deux lettres identifiants un pays (ISO Standard 3166, 1981) donc, fr => France, eng => England...
Nous sommes sur le Site du Zér0, j'ai donc pris le nom à l'envers : sdz.com => com.sdz.
Par exemple, mes packages ont tendance à s'appeler
com.cysboy.<nom>.
Bon, fin de l'aparte.
Cliquez sur "
Finish" pour créer le package. Ensuite, il ne vous reste plus qu'à créer les interfaces de notre diagramme de classe dans ce package (clic droit dessus, "
new / interface").
Et voilà ! Votre package est prêt à l'emploi :
Ce sera donc dans ce package que nous allons développer nos comportements !
Doucement ! Tu ne pourrais pas plutôt nous expliquer un peu plus tout ce mic-mac avec tes interfaces...
J'allais justement le faire.
Comme nous l'avons remarqué tout au long de ce chapitre, les comportements de nos personnages sont trop épars pour être définis dans notre super-classe
Personnage. Vous l'avez dit vous-mêmes, il faudrait que l'on ne puisse modifier que les comportements et non les classes héritant de notre super-classe !
Les interfaces nous servent à créer un super-type d'objet ; ainsi, nous utiliserons des objets de type :
- EspritCombatif qui ont une méthode combat() ;
- Soin qui ont une méthode soigne() ;
- Deplacement qui ont une méthode deplace().
Dans notre classe
Personnage, nous avons ajouté une instance de chaque type de comportement, vous avez dû le remarquer : il y a ces attributs dans notre schéma !

Nous allons développer un comportement par défaut pour chaque type de comportement et nous allons affecter cet objet dans notre super-classe. Les classes filles, elles, auront des instances différentes, correspondant à leur besoin.
Du coup, que fait-on des méthodes de la super-classe Personnage ?
Nous les gardons, mais, au lieu d'avoir une redéfinition de ces dernières, la super-classe va invoquer la méthode de comportement de chaque objet. Ainsi, nous n'avons plus à redéfinir ou à modifier nos classes ! La seule chose qu'il vous reste à faire, c'est d'affecter une instance de comportement à vos objets.
Vous comprendrez mieux avec un exemple. Voici quelques implémentations de comportements.
Implémentations de l'interface EspritCombatif
Code : Java1
2
3
4
5
6
7 | package com.sdz.comportement;
public class Pacifiste implements EspritCombatif {
public void combat() {
System.out.println("Je ne combats pas ! ");
}
}
|
Code : Java1
2
3
4
5
6
7 | package com.sdz.comportement;
public class CombatPistolet implements EspritCombatif{
public void combat() {
System.out.println("Je combats au pitolet !");
}
}
|
Code : Java1
2
3
4
5
6
7 | package com.sdz.comportement;
public class CombatCouteau implements EspritCombatif {
public void combat() {
System.out.println("Je me bats au couteau !");
}
}
|
Implémentations de l'interface Deplacement
Code : Java1
2
3
4
5
6
7 | package com.sdz.comportement;
public class Marcher implements Deplacement {
public void deplacer() {
System.out.println("Je me déplace en marchant.");
}
}
|
Code : Java1
2
3
4
5
6
7 | package com.sdz.comportement;
public class Courir implements Deplacement {
public void deplacer() {
System.out.println("Je me déplace en courant.");
}
}
|
Implémentations de l'interface Soin
Code : Java1
2
3
4
5
6
7 | package com.sdz.comportement;
public class PremierSoin implements Soin {
public void soigne() {
System.out.println("Je donne les premiers soins.");
}
}
|
Code : Java1
2
3
4
5
6
7 | package com.sdz.comportement;
public class Operation implements Soin {
public void soigne() {
System.out.println("Je pratique des opérations !");
}
}
|
Code : Java1
2
3
4
5
6
7 | package com.sdz.comportement;
public class AucunSoin implements Soin {
public void soigne() {
System.out.println("Je ne donne AUCUN soin !");
}
}
|
Voici ce que vous devriez avoir dans votre nouveau package :
Les classes mises dans un package et destinées à être utilisées à l'extérieur du dit package DOIVENT être déclarées public
! !
Sinon, les classes ne seront visible qu'à l'intérieur du package et vous ne pourrez pas les utiliser !
Maintenant que nous avons défini des objets de comportements, nous allons pouvoir remanier notre classe
Personnage.
Nous allons ajouter les variables d'instances, des mutateurs et des constructeurs afin de pouvoir initialiser nos objets.
Voici la nouvelle version de notre super-classe :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 | import com.sdz.comportement.*;
public abstract class Personnage {
//Nos instances de comportements
protected EspritCombatif espritCombatif = new Pacifiste();
protected Soin soin = new AucunSoin();
protected Deplacement deplacement = new Marcher();
/**
* Constructeur par défaut
*/
public Personnage(){}
/**
* Constructeur avec paramètres
* @param espritCombatif
* @param soin
* @param deplacement
*/
public Personnage(EspritCombatif espritCombatif, Soin soin,
Deplacement deplacement) {
this.espritCombatif = espritCombatif;
this.soin = soin;
this.deplacement = deplacement;
}
/**
* Méthode de déplacement de personnage
*/
public void seDeplacer(){
//On utilise les objets de déplacement de façon polymorphe
deplacement.deplacer();
}
/**
* Méthode que les combattants utilisent
*/
public void combattre(){
//On utilise les objets de déplacement de façon polymorphe
espritCombatif.combat();
}
/**
* Méthode de soin
*/
public void soigner(){
//On utilise les objets de déplacement de façon polymorphe
soin.soigne();
}
//************************************************************
// ACCESSEURS
//************************************************************
/**
* Redéfinit le comportement au combat
* @param espritCombatif
*/
protected void setEspritCombatif(EspritCombatif espritCombatif) {
this.espritCombatif = espritCombatif;
}
/**
* Redéfinit le comportement de Soin
* @param soin
*/
protected void setSoin(Soin soin) {
this.soin = soin;
}
/**
* Redéfinit le comportement de déplacement
* @param deplacement
*/
protected void setDeplacement(Deplacement deplacement) {
this.deplacement = deplacement;
}
}
|
Il y a eu du changement depuis le début... Mais maintenant, nous n'utilisons plus des méthodes définies dans notre hiérarchie de classe, mais des implémentations concrètes d'interfaces !
Les méthodes que nos objets appellent utilisent chacune d'elle un objet de comportement. Nous pouvons donc définir des guerriers, des civils, des médecins... tous personnalisables puisqu'il suffit de changer leurs instances de comportements pour que les comportements de ceux-ci changent instantanément. La preuve en image.
Je ne vais pas vous donner les codes de toutes les classes... En voici seulement quelques-unes.
Guerrier.java
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | import com.sdz.comportement.*;
public class Guerrier extends Personnage {
/**
* Constructeur par défaut
*/
public Guerrier(){
this.espritCombatif = new CombatPistolet();
}
/**
* Constructeur personnalisé
* @param espritCombatif
* @param soin
* @param deplacement
*/
public Guerrier(EspritCombatif espritCombatif, Soin soin,
Deplacement deplacement) {
//Appel au constructeur de la super classe
super(espritCombatif, soin, deplacement);
}
}
|
Civil.java
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12 | import com.sdz.comportement.*;
public class Civil extends Personnage{
public Civil() {}
public Civil(EspritCombatif espritCombatif, Soin soin,
Deplacement deplacement) {
super(espritCombatif, soin, deplacement);
}
}
|
Medecin.java
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14 | import com.sdz.comportement.*;
public class Medecin extends Personnage{
public Medecin() {
this.soin = new PremierSoin();
}
public Medecin(EspritCombatif espritCombatif, Soin soin,
Deplacement deplacement) {
super(espritCombatif, soin, deplacement);
}
}
|
N'oubliez pas d'importer le package contenant nos classes de comportement !
Maintenant, voici un exemple d'utilisation :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13 | class Test{
public static void main(String[] args) {
Personnage[] tPers = {new Guerrier(), new Civil(), new Medecin()};
for(int i = 0; i < tPers.length; i++){
System.out.println("\nInstance de " + tPers[i].getClass().getName());
System.out.println("*****************************************");
tPers[i].combattre();
tPers[i].seDeplacer();
tPers[i].soigner();
}
}
}
|
Le résultat de ce code nous donne :
Vous pouvez voir que nos personnages ont tous un comportement par défaut qui leur conviennent bien !
Nous avons spécifié, dans le cas où c'est nécessaire, le comportement par défaut d'un personnage dans son constructeur par défaut :
- le guerrier se bat avec un pistolet ;
- le médecin soigne.
Or, voyons comment dire à nos personnages de faire autre chose... Que diriez-vous de faire faire une petite opération chirurgicale à notre objet
Guerrier ? Pour ce faire, vous pouvez redéfinir son comportement de soin avec son mutateur, présent dans la super-classe :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | import com.sdz.comportement.*;
class Test{
public static void main(String[] args) {
Personnage[] tPers = {new Guerrier(), new Civil(), new Medecin()};
for(int i = 0; i < tPers.length; i++){
System.out.println("\nInstance de " + tPers[i].getClass().getName());
System.out.println("*****************************************");
tPers[i].combattre();
tPers[i].seDeplacer();
tPers[i].soigner();
if(tPers[i].getClass().getName().equals("Guerrier")){
tPers[i].setSoin(new Operation());
System.out.print(" \t Après modification de comportement de soin : \n \t\t");
tPers[i].soigner();
}
}
}
}
|
Ce qui nous donne :
Vous voyez que le comportement de soin de notre objet a changé dynamiquement, sans que nous ayons besoin de changer la moindre ligne de son code source !

Le plus beau dans le fait de travailler comme ceci, c'est que vous pouvez tout à fait instancier des
Guerrier avec des comportements différents très simplement, mais vous pouvez aussi leur donner des comportements que vous codez à la volée !
Regardez ceci :
Code : Java 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 | import com.sdz.comportement.*;
class Test{
public static void main(String[] args) {
Personnage civil = new Civil();
System.out.println("Comportement par défaut d'un civil : ");
System.out.println("****************************************");
civil.combattre();
civil.soigner();
civil.seDeplacer();
System.out.println("\nTransformation d'un civil : ");
System.out.println("****************************************");
civil.setDeplacement(new Deplacement(){
public void deplacer() {
System.out.println("Je saute sur tout ce qui bouge ! ! ! !");
}
});
civil.setSoin(new Soin(){
public void soigne() {
System.out.println("L'amputation est ma grande passion ! ! !");
}
});
civil.setEspritCombatif(new EspritCombatif(){
public void combat() {
System.out.println("Je roule en char d'assaut ! ! ATTENTION DEVANT ! ! !");
}
});
civil.combattre();
civil.soigner();
civil.seDeplacer();
}
}
|
Ce qui donne, au final :
Vous avez pu constater que vous n'avez plus de code dupliqué !

Les modifications de comportement deviennent très simples à faire et vous n'avez plus à modifier le code source de votre classe
Personnage en cas de changements...
Je suppose que, maintenant que vous avez vu cet exemple, vous avez deviné où et quand vous avez utilisé le pattern strategy !
Lorsque vous programmiez des implémentations de
ActionListener pour la gestion de vos événements...

Sauf que, dans ce cas, il y a une nuance. Vous avez utilisé le pattern strategy pour créer des comportements lors d'événements sur votre IHM, mais ces interfaces de gestion d'événements sont utilisées dans un autre pattern :
le pattern observer !
Nous aborderons ce dernier très bientôt...
Bon, je crois qu'un petit topo s'impose...