Description
The template design pattern allows you to add a skeleton of an algorithm at the parent class level. This allows subclasses to be more flexible, they can add or redefine features without changing the structure.
Your algorithm is broken down into a series of steps represented by methods that will be accessible through the template. These methods can be:
- Abstract
- Implemented
Thus, we can override these methods to adapt them to different algorithms.
Scenario
👾 You are developing a series of games, these games all have a precise way of working:
- We retrieve the game assets.
- Game initialization.
- We start the game.
- We add new characters according to the game.
- We stop the game.
In other words, depending on the game, we have different behaviors.
Pros and Cons
Pros | Cons |
---|---|
✔️Flexibility | ❌Limitation: adding the skeleton may limit the client's actions. |
✔️Reduces redundancy | ❌Maintainability: the more steps in the algorithm, the more complicated it becomes to maintain. |
When to use it?
💡 You want to create algorithms whose complexity can be significant. 💡 You have classes where there is code duplication.
UML Diagram
In our diagram, adding a game is very simple, you just need to respect the implemented algorithm and add the games you want to it.
Implementation
We must first define our template: Game
. This class defines several abstract methods for setting up the game. These methods will be used to launch the game through the play
method.
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;
}
}
We need some explanations:
addNewGameCharacterToTheGame
: allows to create a hook in the template.play
: this method cannot be changed by the subclasses, because it allows the client to use the desired algorithm with a common behavior.FootballGame
: a class that extends theGame
template to implement the specificities of the football game.
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;
}
}
We can now define another game:
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");
}
}
We just need to test our methods:
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();
}
}
Result:
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...
We have finally finished, what can we notice?
- We have a method that allows us to launch our entire algorithm.
- Our algorithms implement the behavior of the methods without impacting the other algorithms.
- Our common methods are gathered in the abstract class.