Pattern Observer

Le pattern Observer, c'est un concept super utile en programmation, surtout quand tu veux que des parties de ton programme réagissent automatiquement à des changements qui se produisent quelque part ailleurs dans le code. Imagine que tu as un système où certains événements, comme des clics de souris ou des mises à jour de données, doivent être suivis par différentes parties de ton application. Plutôt que de mettre ton nez partout pour vérifier si quelque chose a changé, tu utilises le pattern Observer.

Voici comment ça fonctionne :

  1. observables (Sujet) : C'est la partie de ton programme qui va subir des changements. Par exemple, un modèle de données que tu modifies.

  2. Observers (Observateurs) : Ce sont les entités qui veulent être informées lorsque quelque chose change dans l'Observable. Ça pourrait être n'importe quelle partie de ton programme qui doit réagir à ces changements, comme une interface utilisateur qui doit se mettre à jour.

Le processus est le suivant :

  • Inscription : Les Observers s'inscrivent auprès de l'Observable pour dire "Hé, préviens-moi quand tu changes !".
  • Notification : Quand l'Observable change (par exemple, une donnée est mise à jour), il envoie automatiquement une notification à tous les Observers inscrits.
  • Réaction : Chaque Observer reçoit cette notification et réagit en conséquence, comme rafraîchir l'affichage, enregistrer des logs, etc.

L'avantage ici, c'est que l'Observable n'a pas besoin de savoir qui sont les Observers ni ce qu'ils font. Il se contente de les prévenir. Cela rend ton code moins couplé (Couplage), plus modulaire, et plus facile à gérer et à étendre.

Pour te donner un exemple concret, imagine une application météo :

  • Observable : Station météo qui enregistre des données climatiques.
  • Observers : Une application mobile, un site web, et une alerte email qui doivent tous mettre à jour leurs contenus dès que la station météo signale un changement, comme une chute soudaine de température.

En utilisant le pattern Observer, tu rends ton application flexible et réactive, capable de gérer des changements en temps réel sans que les différentes parties ne soient trop dépendantes les unes des autres.

Bien sûr ! Voici un exemple simple d'implémentation du pattern Observer en TypeScript. On va créer un système où un objet WeatherStation (la station météo), qui est l'Observable, informera ses Observers chaque fois que la température change.

Étape 1 : Définir les interfaces pour Observable et Observer

interface Observer {
    update(temperature: number): void;
}

interface Observable {
    registerObserver(o: Observer): void;
    removeObserver(o: Observer): void;
    notifyObservers(): void;
}

Étape 2 : Créer la classe ConcreteObservable

La classe WeatherStation implémente l'interface Observable et peut enregistrer, retirer et notifier les Observers.

class WeatherStation implements Observable {
    private observers: Observer[] = [];
    private temperature: number = 0;

    public registerObserver(o: Observer): void {
        this.observers.push(o);
    }

    public removeObserver(o: Observer): void {
        const index = this.observers.indexOf(o);
        if (index >= 0) {
            this.observers.splice(index, 1);
        }
    }

    public notifyObservers(): void {
        for (let observer of this.observers) {
            observer.update(this.temperature);
        }
    }

    public setTemperature(temp: number): void {
        console.log(`WeatherStation: nouvelle température enregistrée = ${temp}`);
        this.temperature = temp;
        this.notifyObservers();
    }
}

Étape 3 : Créer les ConcreteObservers

Ces classes implémentent l'interface Observer. Chaque observer va réagir à la mise à jour de la température.

class TemperatureDisplay implements Observer {
    public update(temperature: number): void {
        console.log(`TemperatureDisplay: la température est maintenant à ${temperature}°C`);
    }
}

class Fan implements Observer {
    public update(temperature: number): void {
        if (temperature > 25) {
            console.log("Fan: Il fait chaud ici, donc je m'allume.");
        } else {
            console.log("Fan: Il fait agréable, donc je reste éteint.");
        }
    }
}

Étape 4 : Tester le système

Créons une instance de WeatherStation et ajoutons quelques observers. Ensuite, nous changerons la température pour voir comment les observers réagissent.

let weatherStation = new WeatherStation();
let tempDisplay = new TemperatureDisplay();
let fan = new Fan();

weatherStation.registerObserver(tempDisplay);
weatherStation.registerObserver(fan);

weatherStation.setTemperature(20);
weatherStation.setTemperature(30);

Résultat attendu dans la console

Quand tu exécutes ce code, tu devrais voir :

WeatherStation: nouvelle température enregistrée = 20
TemperatureDisplay: la température est maintenant à 20°C
Fan: Il fait agréable, donc je reste éteint.
WeatherStation: nouvelle température enregistrée = 30
TemperatureDisplay: la température est maintenant à 30°C
Fan: Il fait chaud ici, donc je m'allume.

Cet exemple montre comment le pattern Observer permet aux différentes parties de ton application de réagir automatiquement à des changements d'état sans être directement connectées les unes aux autres.

Pour approfondir ta compréhension des patterns de conception et d'autres concepts avancés en programmation, voici une liste de notions et sujets intéressants à explorer :

  1. Autres Patterns de Conception :

    • Pattern Singleton : pour restreindre l'instanciation d'une classe à un seul objet.
    • Pattern Factory : pour créer des objets sans spécifier la classe exacte de l'objet qui sera créé.
    • Pattern Decorator : pour ajouter dynamiquement des responsabilités supplémentaires à des objets.
    • Pattern Strategy : pour permettre à un objet de changer son comportement en changeant ses stratégies.
    • Pattern Command : pour encapsuler une requête en tant qu'objet, permettant ainsi de paramétrer les clients avec différentes requêtes.
    • Pattern Adapter : pour convertir l'interface d'une classe en une autre interface que le client attend.
  2. Programmation Fonctionnelle :

    • Fonctions pures : fonctions qui ne modifient pas les états extérieurs et retournent toujours le même résultat pour les mêmes arguments.
    • High-Order Functions : fonctions qui prennent une ou plusieurs fonctions en arguments ou qui renvoient une fonction.
    • Immutabilité : pratique qui consiste à rendre les objets inaltérables après leur création.
  3. Programmation Réactive :

    • Observables et Streams : comprendre la manipulation des séquences de données asynchrones avec RxJS ou autres bibliothèques similaires.
    • Backpressure : gestion de la surcharge de données dans les flux de données asynchrones.
  4. Principes SOLID : cinq principes de la programmation orientée objet et de la conception agile pour rendre le logiciel plus compréhensible, flexible et maintenable.

  5. Architecture Logicielle :

    • Clean Architecture : pour concevoir des systèmes indépendants des frameworks, de l'UI, de la base de données.
    • Microservices : pour comprendre comment développer des applications en tant qu'ensemble de petits services qui communiquent via des API.
    • Event-driven Architecture : architecture basée sur des événements pour faciliter la communication asynchrone entre différentes parties d'un système.
  6. Domain-Driven Design :

    • Concepts de base tels que les entités, les valeurs objets, les agrégats et les domain events pour structurer mieux le code autour du domaine métier.
  7. Patterns Asynchrones :

    • futures and Promises : pour la gestion de résultats asynchrones.
    • Coroutines : pour simplifier le code asynchrone en utilisant des fonctions qui peuvent être suspendues et reprises.
  8. Concurrency Patterns :

    • Pattern Producer-Consumer, Pattern Readers-Writers, et Dining Philosophers pour gérer les interactions complexes entre Threads concurrents.

Chacun de ces sujets t'ouvrira des perspectives différentes sur la manière de concevoir et d'implémenter des logiciels, enrichissant ainsi ta boîte à outils de développeur et te permettant de mieux répondre aux défis de la programmation moderne.