Le Pattern Observer expliqué simplement
L’Observer Pattern définit une relation un-à-plusieurs : quand un objet change d’état, tous ses abonnés sont notifiés automatiquement, sans que l’émetteur ait besoin de connaître leur nature exacte. C’est le pattern derrière les event listeners et les systèmes pub/sub.
Sommaire
Le problème #
Quand le statut d’une commande change — payée, expédiée, livrée — plusieurs parties du système doivent réagir : envoyer un email au client, envoyer un SMS, notifier l’entrepôt pour préparer l’envoi. Si la classe Commande appelle directement chacun de ces services dans sa méthode changerStatut(), elle devient couplée à tous ses « abonnés ». Chaque nouveau besoin de notification oblige alors à rouvrir cette classe centrale et à la modifier, encore et encore.
L’idée générale #
L’Observer définit une relation un-à-plusieurs : quand l’état d’un objet (le Subject) change, tous les objets qui s’y sont abonnés (les Observers) sont automatiquement notifiés, sans que le Subject ait besoin de connaître leur nature exacte.
Trois éléments composent le pattern :
- Subject : l’objet observé, qui maintient une liste d’observateurs et les notifie à chaque changement pertinent.
- Observer : l’interface commune que chaque abonné implémente, souvent une seule méthode (
notifier()). - ConcreteObserver : chaque implémentation concrète, qui réagit à sa manière à la notification reçue.
Analogie du quotidien #
C’est comme s’abonner à une chaîne YouTube. Le créateur de contenu (le Subject) ne connaît pas individuellement chaque abonné : il publie simplement une nouvelle vidéo. Tous les abonnés (les Observers) reçoivent une notification automatiquement, chacun réagissant à sa façon — l’un regarde immédiatement, l’autre ignore la notification et regardera plus tard. Le créateur peut gagner ou perdre des abonnés à tout moment sans jamais avoir à changer sa façon de publier du contenu.
Diagramme #
Exemple de code #
PHP #
interface Observer
{
public function notifier(string $statut): void;
}
final class NotificationEmail implements Observer
{
public function notifier(string $statut): void
{
echo "Email : votre commande est maintenant '{$statut}'" . PHP_EOL;
}
}
final class NotificationSms implements Observer
{
public function notifier(string $statut): void
{
echo "SMS : statut mis à jour -> {$statut}" . PHP_EOL;
}
}
final class ServiceEntrepot implements Observer
{
public function notifier(string $statut): void
{
if ($statut === 'payee') {
echo "Entrepôt : préparation de l'envoi lancée" . PHP_EOL;
}
}
}
final class SuiviCommande
{
/** @var Observer[] */
private array $observers = [];
public function abonner(Observer $observer): void
{
$this->observers[] = $observer;
}
public function desabonner(Observer $observer): void
{
$this->observers = array_filter($this->observers, fn ($o) => $o !== $observer);
}
public function changerStatut(string $statut): void
{
foreach ($this->observers as $observer) {
$observer->notifier($statut);
}
}
}
// Utilisation
$commande = new SuiviCommande();
$commande->abonner(new NotificationEmail());
$commande->abonner(new NotificationSms());
$commande->abonner(new ServiceEntrepot());
$commande->changerStatut('payee');
// Les trois observateurs réagissent, chacun à sa manière
Java #
interface Observer {
void notifier(String statut);
}
class NotificationEmail implements Observer {
public void notifier(String statut) {
System.out.println("Email : votre commande est maintenant '" + statut + "'");
}
}
class NotificationSms implements Observer {
public void notifier(String statut) {
System.out.println("SMS : statut mis à jour -> " + statut);
}
}
class ServiceEntrepot implements Observer {
public void notifier(String statut) {
if (statut.equals("payee")) {
System.out.println("Entrepôt : préparation de l'envoi lancée");
}
}
}
class SuiviCommande {
private final List<Observer> observers = new ArrayList<>();
public void abonner(Observer observer) {
observers.add(observer);
}
public void desabonner(Observer observer) {
observers.remove(observer);
}
public void changerStatut(String statut) {
for (Observer observer : observers) {
observer.notifier(statut);
}
}
}
// Utilisation
SuiviCommande commande = new SuiviCommande();
commande.abonner(new NotificationEmail());
commande.abonner(new NotificationSms());
commande.abonner(new ServiceEntrepot());
commande.changerStatut("payee");
// Les trois observateurs réagissent, chacun à sa manière
JavaScript #
class NotificationEmail {
notifier(statut) {
console.log(`Email : votre commande est maintenant '${statut}'`);
}
}
class NotificationSms {
notifier(statut) {
console.log(`SMS : statut mis à jour -> ${statut}`);
}
}
class ServiceEntrepot {
notifier(statut) {
if (statut === "payee") {
console.log("Entrepôt : préparation de l'envoi lancée");
}
}
}
class SuiviCommande {
#observers = [];
abonner(observer) {
this.#observers.push(observer);
}
desabonner(observer) {
this.#observers = this.#observers.filter((o) => o !== observer);
}
changerStatut(statut) {
this.#observers.forEach((observer) => observer.notifier(statut));
}
}
// Utilisation
const commande = new SuiviCommande();
commande.abonner(new NotificationEmail());
commande.abonner(new NotificationSms());
commande.abonner(new ServiceEntrepot());
commande.changerStatut("payee");
// Les trois observateurs réagissent, chacun à sa manière
Quand utiliser ce pattern ? #
- Quand plusieurs parties du système doivent réagir à un même événement, sans que l’émetteur ait besoin de les connaître individuellement.
- Quand le nombre d’abonnés peut varier dans le temps, avec des ajouts ou des retraits à l’exécution.
- Quand on veut découpler l’objet qui déclenche un changement de ceux qui y réagissent, pour pouvoir les faire évoluer indépendamment.
Évitez Observer si un seul destinataire réagit toujours de la même façon : un appel de méthode direct est plus simple et plus facile à suivre.
Points importants #
- Observer est la base théorique des event listeners du DOM (
addEventListener), desEventEmitterde Node.js, et des systèmes pub/sub — le pattern est omniprésent, même sans être nommé explicitement. - Attention à l’ordre de notification : si les observateurs doivent s’exécuter dans un ordre précis ou en parallèle, c’est au Subject de le gérer explicitement.
- Un Observer qui lève une exception peut interrompre la notification des suivants si rien n’est prévu pour l’isoler : pensez à englober chaque notification dans un
try/catchsi l’un des abonnés ne doit pas bloquer les autres. - Toujours prévoir un moyen de se désabonner (
desabonner) : l’oublier est une cause fréquente de fuite mémoire, notamment en JavaScript avec les écouteurs d’événements jamais retirés.
🐻 À retenir
- ●Observer notifie automatiquement tous les abonnés d’un changement d’état, sans que le Subject ait besoin de les connaître individuellement.
- ●C’est le pattern derrière
addEventListeneretEventEmitter: vous l’utilisez probablement déjà sans le nommer. - ●Ne pas oublier le désabonnement (
removeEventListener/desabonner) : c’est la cause la plus fréquente de fuite mémoire liée à ce pattern.