Le patron Observer (GoF) permet à un objet d'être informé du changement d'état d'un autre objet sans créer de couplage fort entre les deux classes.
Il y a deux idées clé:
Exemples:
public class Game{ private String homeTeam; private int scoreHomeTeam; private String awayTeam; private int scoreAwayTeam; public String getHomeTeam() { ... } public String getAwayTeam() { ... } public int getHomeTeamScore() { ... } public int getAwayTeamScore() { ... } public void startGame(){ ... } public void endGame() { ... } public void setScore(int scoreHomeTeam, int scoreVisitorTeam) { ... } }
On veut:
public interface GameObserver{ public void onGameStart(Game game); public void onGameStop(Game game); public void onScoreChange(Game game); }
public class Game { private String homeTeam; private int scoreHomeTeam; private String awayTeam; private int scoreAwayTeam; private final List<GameObserver> observers = new ArrayList<>(); public void register(GameObserver obs){ observers.add(Objects.requireNonNull(obs)); } public void unregister(GameObserver obs){ if (!observers.remove(Objects.requireNonNull(obs))){ throw new IllegalStateException(); } } public void startGame(){ notifyGameStart(); // ... } private void notifyGameStart(){ for(var obs : observers){ // notify all observers obs.onGameStart(this); } } // ... }
L'observer peut être intéressé seulement par certaines notifications.
public class TwitterObserver implements GameObserver { @Override public void onGameStart(Game game){ // nothing } @Override public void onGameStop(Game game){ // nothing } @Override public void onScoreChange(Game game){ TwitterAPI.tweet(game.getHomeTeam() + ": " + game.getHomeTeamScore() + " versus " + game.getAwayTeam() + ": " + game.getAwayTeamScore()); } }
envoyer un Tweet quand il y a des buts
public interface GameObserver { public default void onGameStart(Game game) { } public default void onGameStop(Game game) { } public default void onScoreChange(Game game) { } }
... ce qui permet de ne spécifier que ce qui nous intéresse.
public class TwitterObserver implements GameObserver { @Override public void onScoreChange(Game game){ TwitterAPI.tweet(game.getHomeTeam() + ": " + game.getHomeTeamScore() + " versus " + game.getAwayTeam() + ": " + game.getAwayTeamScore()); } }
envoyer un Tweet quand il y a des buts
L'observer peut nécessiter de maintenir un état.
public class SummaryObserver implements GameObserver { private int scoreChanges; @Override public void onGameStop(Game game){ PublishAPI.post("After " + scoreChanges + " score changes, the game ends with " + game.getHomeTeamScore() + " for " + game.getHomeTeam() + " versus " + game.getAwayTeamScore() + " for " + game.getAwayTeam()); } @Override public void onScoreChange(Game game){ scoreChanges++; } }
poster un article avec le score à la fin du match
L'observer peut offrir d'autres méthodes.
public class PDFGeneratorObserver implements GameObserver { private final PdfGenerator<GameReportStyle> pdfGen = new PdfGenerator<>(); public void onGameStart(Game game){ pdfGen.addTitle(game.getHomeTeam() + " versus " + game.getAwayTeam()); pdfGen.addParagraph("Game starts"); } public void onGameStop(Game game){ pdfGen.addParagraph("Game ends between " + game.getHomeTeam() + " and " + game.getAwayTeam()); pdfGen.addParagraph("Final score: " + game.getHomeTeamScore() + "/" + game.getAwayTeamScore()); } public void onScoreChange(Game game){ pdfGen.addParagraph("Score changes: " + game.getHomeTeamScore() + "/" + game.getAwayTeamScore()); } public void saveReportInFile(Path pdfFile) throws IOException { pdfGen.writeIn(pdfFile); } }
écrire le déroulé du match dans un fichier PDF...
Dans notre exemple, les observeurs sont pull, c'est à dire qu'on les notifie et qu'ils vont chercher les informations dans l'objet observé.
On peut passer les informations (par exemple, le score) au moment de la notification, on parle d'observers push.
Avantages/Inconvénients
⇛ Comme toujours avec les design patterns, on peut faire un mix des deux et adapter aux besoins.
En Pull, on donne plus de flexibilité aux observateurs mais on doit faire des méthodes publiques pour que l'observateur est accès aux informations. On affaiblit l'encapsulation.
En Push, on construit l'ensemble des informations dont l'observeur a besoin. Cela peut-être coûteux et c'est moins flexible.
Il n'y a pas de bonne ou mauvaise solution, il faut réfléchir à chaque fois.