Digital solutions
Design Patterns avec Laravel

Observer Pattern

  • Type : Comportemental
  • Difficulté : 5/10
  • Définition succincte : Le Observer Pattern est un design pattern comportemental qui permet à un objet (le subject) de notifier automatiquement un ou plusieurs autres objets (observers) lorsqu'un changement d'état se produit. Cela permet de créer un système réactif où les objets qui dépendent d'un autre peuvent être informés et agir en conséquence sans être étroitement couplés.
  • Contexte spécifique à Laravel : En Laravel, le Observer Pattern est utilisé dans les Eloquent Observers pour réagir à des événements du cycle de vie des modèles (comme la création, la mise à jour ou la suppression d'un modèle).

Objectif du Observer Pattern

L'objectif principal du Observer Pattern est de permettre la notification automatique des objets dépendants lorsque l'état de l'objet observé change. Ce pattern est particulièrement utile lorsque plusieurs objets doivent suivre les modifications d'un autre objet, sans avoir besoin de savoir comment les objets sont liés ou comment la notification est implémentée.

Structure du Observer Pattern

  • Subject : L'objet qui est observé pour les changements d'état.
  • Observer : L'objet qui est notifié lorsque l'état du subject change.
  • ConcreteSubject : La classe concrète qui implémente le subject.
  • ConcreteObserver : La classe concrète qui implémente l'observer et réagit aux notifications du subject.

1. Exemple d'utilisation dans Laravel

En Laravel, le Observer Pattern est implémenté de manière native dans les Eloquent Observers. Les Eloquent Observers permettent de réagir à des événements du cycle de vie des modèles, tels que la création, la mise à jour ou la suppression.

Voici un exemple de la manière dont un Eloquent Observer est utilisé pour suivre les modifications sur un modèle User.

a) Création d'un Observer

// app/Observers/UserObserver.php

namespace App\Observers;

use App\Models\User;

class UserObserver
{
    // Méthode appelée avant la création d'un utilisateur
    public function creating(User $user)
    {
        \Log::info("Creating user: {$user->name}");
    }

    // Méthode appelée après la suppression d'un utilisateur
    public function deleted(User $user)
    {
        \Log::info("Deleted user: {$user->name}");
    }
}

b) Enregistrement de l'Observer

Tu dois enregistrer cet observer pour qu'il puisse commencer à écouter les événements du modèle User. Cela se fait généralement dans un service provider.

// app/Providers/AppServiceProvider.php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Models\User;
use App\Observers\UserObserver;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        // Enregistrer l'observer pour le modèle User
        User::observe(UserObserver::class);
    }
}

Maintenant, chaque fois qu'un utilisateur est créé ou supprimé, les méthodes de l'observer creating et deleted seront appelées, permettant d'ajouter une logique supplémentaire, comme la journalisation ou l'envoi de notifications.

2. Implémentation complète dans Laravel

Imaginons que tu souhaites observer les changements d'état d'une station météorologique. La station météorologique va informer plusieurs systèmes (comme une interface utilisateur ou un système d'alerte) lorsque la température ou la pression atmosphérique changent. Nous allons utiliser le Observer Pattern pour notifier ces systèmes automatiquement.

a) Créer l'interface Subject

Le subject est la station météorologique qui doit informer les observateurs des changements.

// app/Contracts/Subject.php

namespace App\Contracts;

interface Subject
{
    public function attach(Observer $observer): void;
    public function detach(Observer $observer): void;
    public function notify(): void;
}

b) Créer l'interface Observer

L'observer représente les objets qui vont réagir aux notifications du subject. Chaque observer doit implémenter une méthode update().

// app/Contracts/Observer.php

namespace App\Contracts;

interface Observer
{
    public function update(float $temperature, float $pressure): void;
}

c) Implémenter le Subject concret WeatherStation

La station météorologique joue le rôle du subject et maintient une liste des observers qui doivent être notifiés lorsque la température ou la pression change.

// app/Services/WeatherStation.php

namespace App\Services;

use App\Contracts\Subject;
use App\Contracts\Observer;

class WeatherStation implements Subject
{
    private $observers = [];
    private $temperature;
    private $pressure;

    public function attach(Observer $observer): void
    {
        $this->observers[] = $observer;
    }

    public function detach(Observer $observer): void
    {
        $this->observers = array_filter($this->observers, fn ($obs) => $obs !== $observer);
    }

    public function notify(): void
    {
        foreach ($this->observers as $observer) {
            $observer->update($this->temperature, $this->pressure);
        }
    }

    // Méthode pour simuler un changement de conditions météorologiques
    public function setConditions(float $temperature, float $pressure): void
    {
        $this->temperature = $temperature;
        $this->pressure = $pressure;
        $this->notify(); // Notifier les observers des changements
    }
}

d) Créer des Observers concrets

Nous allons maintenant créer deux observateurs : un UserInterface qui affiche les données sur une interface utilisateur, et un AlertSystem qui déclenche des alertes basées sur les conditions météorologiques.

1. Interface utilisateur (UserInterface)

Cet observateur simule l'affichage des données de la station météorologique sur une interface.

// app/Observers/UserInterface.php

namespace App\Observers;

use App\Contracts\Observer;

class UserInterface implements Observer
{
    public function update(float $temperature, float $pressure): void
    {
        echo "UI: Temperature: {$temperature}°C, Pressure: {$pressure} hPa" . PHP_EOL;
    }
}
2. Système d'alerte (AlertSystem)

Cet observateur déclenche une alerte si la température descend en dessous de 0°C.

// app/Observers/AlertSystem.php

namespace App\Observers;

use App\Contracts\Observer;

class AlertSystem implements Observer
{
    public function update(float $temperature, float $pressure): void
    {
        if ($temperature < 0) {
            echo "Alert: Temperature below freezing! ({$temperature}°C)" . PHP_EOL;
        }
    }
}

e) Utiliser le Observer Pattern dans une commande CLI

Nous allons maintenant utiliser ces classes dans une commande CLI. La station météorologique changera ses conditions, et les observateurs réagiront automatiquement.

// app/Console/Commands/MonitorWeather.php

namespace App\Console\Commands;

use Illuminate\Console\Command;
use App\Services\WeatherStation;
use App\Observers\UserInterface;
use App\Observers\AlertSystem;

class MonitorWeather extends Command
{
    protected $signature = 'weather:monitor';
    protected $description = 'Simulate weather monitoring with observers';

    public function handle()
    {
        // Créer la station météo (subject)
        $weatherStation = new WeatherStation();

        // Attacher les observateurs
        $ui = new UserInterface();
        $alertSystem = new AlertSystem();

        $weatherStation->attach($ui);
        $weatherStation->attach($alertSystem);

        // Simuler un changement de conditions météorologiques
        $this->info("Setting weather to 10°C, 1013 hPa");
        $weatherStation->setConditions(10, 1013);

        $this->info("Setting weather to -5°C, 1010 hPa");
        $weatherStation->setConditions(-5, 1010);

        // Détacher l'interface utilisateur
        $weatherStation->detach($ui);

        $this->info("Setting weather to 25°C, 1008 hPa (without UI)");
        $weatherStation->setConditions(25, 1008);
    }
}

f) Exécution de la commande

Tu peux maintenant exécuter la commande pour voir comment les observateurs réagissent aux changements météorologiques :

php artisan weather:monitor

Cela produira quelque chose comme ceci :

Setting weather to 10°C, 1013 hPa
UI: Temperature: 10°C, Pressure: 1013 hPa
Setting weather to -5°C, 1010 hPa
UI: Temperature: -5°C, Pressure: 1010 hPa
Alert: Temperature below freezing! (-5°C)
Setting weather to 25°C, 1008 hPa (without UI)
Alert: Temperature below freezing! (-5°C)

Avantages du Observer Pattern

  1. Réactivité : Le Observer Pattern permet de réagir automatiquement aux changements dans le système sans coupler fortement les objets entre eux.
  2. Séparation des responsabilités : Les objets observers peuvent être isolés de la logique principale, facilitant la maintenance du code et le respect du principe de responsabilité unique.
  3. Évolutivité : Il est facile d'ajouter de nouveaux observers pour suivre les mêmes événements sans changer le code de la classe subject.

Inconvénients du Observer Pattern

  1. Complexité accrue : Avec plusieurs observers, il peut devenir difficile de suivre quelles actions sont déclenchées par quels événements.
  2. Dépendance implicite : Les observers créent des dépendances implicites entre les objets, ce qui peut rendre le système plus difficile à comprendre et à déboguer.
  3. Performances : Si un grand nombre d'observers réagissent à des événements fréquents, cela peut affecter les performances du système.

Conclusion

Le Observer Pattern est extrêmement utile pour les systèmes réactifs où les objets doivent être automatiquement informés des changements d'état d'autres objets. En Laravel, ce pattern est intégré directement via les Eloquent Observers, permettant de réagir facilement aux événements du cycle de vie des modèles. Ce pattern permet d'améliorer la modularité et de séparer les préoccupations, rendant le code plus facile à maintenir et à étendre.