Description
Le design pattern template permet d'ajouter un squelette d'un algorithme au niveau de la classe mère. Cela permet aux sous-classes d'être plus flexibles, elles peuvent ajouter ou redéfinir des fonctionnalités sans changer la structure.
Votre algorithme est décomposé en série d'étapes représentées par des méthodes qui seront accessibles par le template. Ces méthodes peuvent être :
- Abstraite
- Implémentée
Ainsi, on pourra overrider ces méthodes pour les adapter aux différents algorithmes.
Mise en situation
👾 Vous développez une série de jeux, ces jeux ont tous une manière précise de fonctionner :
- On récupère les assets du jeu.
- Initialisation du jeu.
- On démarre le jeu.
- On ajoute de nouveaux caractères en fonction du jeu.
- On arrête le jeu.
En d'autres termes, en fonction du jeu, on a des comportements différents.
Avantages et inconvénients
| Avantages | Inconvénients |
|---|---|
| ✔️Flexibilité | ❌Limitation : l'ajout du squelette peut limiter le client dans ses actions. |
| ✔️Diminue la redondance | ❌Maintenabilité : plus on a d'étapes dans l'algorithme, plus le maintenir devient compliqué. |
Quand l'utiliser ?
💡 Vous voulez créer des algorithmes dont la complexité peut être importante. 💡 Vous avez des classes dont il existe de la duplication de code.
Diagramme UML

Dans notre diagramme, l'ajout de jeu est très simple, il suffit de respecter l'algorithme mis en œuvre et d'y ajouter les jeux que l'on veut.
Implémentation
Nous devons dans un premier temps définir notre template : Game. Cette classe définit plusieurs méthodes abstraites permettant la mise en place du jeu. Ces méthodes serviront à lancer le jeu à travers la méthode play.
Game
package com.design.template.base;
public abstract class Game {
protected abstract void initialize();
protected abstract void startPlay();
protected abstract void endPlay();
//Hooked on template
protected abstract void addNewGameCharacterToTheGame();
// Template method les sous classes ne peuvent pas les changer (important) => template
public final void play() {
loadAssets();
initialize();
startPlay();
if(addNewGameCharacter()) {
addNewGameCharacterToTheGame();
}
endPlay();
}
void loadAssets() {
System.out.println("Loading Game Assets !");
}
boolean addNewGameCharacter() {
return true;
}
}
Nous avons besoin de quelques explications :
addNewGameCharacterToTheGame: permet de créer un hook dans le template.play: cette méthode ne peut pas être changée par les sous-classes, car elle permet au client d'utiliser l'algorithme souhaité avec un comportement commun.FootballGame: une classe qui étend le templateGamepour mettre en place les spécificités du jeu de football.
FootballGame
package com.design.template.model;
import com.design.template.base.Game;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class FootballGame extends Game {
@Override
protected void initialize() {
System.out.println("Football Game init...");
}
@Override
protected void startPlay() {
System.out.println("Football Game starting...");
playerWantsNewCharacter();
}
@Override
protected void endPlay() {
System.out.println("Football Game ending...");
}
@Override
protected void addNewGameCharacterToTheGame() {
System.out.println("Adding new Character to the game");
}
public boolean playerWantsNewCharacter() {
String answer = getUserInput();
return answer.toLowerCase().startsWith("y");
}
private String getUserInput() {
String answer = "no";
System.out.println("Would you like to add a new character to the game ? (y/n)");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
}catch (IOException ioe) {
System.out.println("IO Error");
}
return answer;
}
}
Nous pouvons maintenant définir un autre jeu :
MmoGame
package com.design.template.model;
import com.design.template.base.Game;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Locale;
public class MmoGame extends Game {
@Override
protected void initialize() {
System.out.println("Mmo Game init...");
}
@Override
protected void startPlay() {
System.out.println("Mmo Game starting...");
}
@Override
protected void endPlay() {
System.out.println("Mmo Game ending...");
}
@Override
protected void addNewGameCharacterToTheGame() {
System.out.println("No new Character to the game");
}
}
Il ne nous reste plus qu'à tester nos méthodes :
Main
package com.design.template;
import com.design.template.base.Game;
import com.design.template.model.MmoGame;
import com.design.template.model.FootballGame;
public class Main {
public static void main(String[] args) {
Game game = new FootballGame();
game.play();
System.out.println("=======");
game = new MmoGame();
game.play();
}
}
Resultat:
Loading Game Assets !
Football Game init...
Football Game starting...
Would you like to add a new character to the game ? (y/n)
y
Adding new Character to the game
Football Game ending...
Loading Game Assets !
Mmo Game init...
Mmo Game starting...
No new Character to the game
Mmo Game ending...
Nous avons enfin terminé, que pouvons-nous remarquer ?
- Nous avons une méthode qui permet de lancer l'ensemble de notre algorithme.
- Nos algorithmes implémentent le comportement des méthodes sans impacter les autres algorithmes.
- Nos méthodes communes sont réunies dans la classe abstraite.





