Introduzione
Il Pattern Observer è un approccio molto utile quando un oggetto deve informare automaticamente altri oggetti ogni volta che accade un evento. L’oggetto osservato viene chiamato Subject, mentre gli oggetti che ricevono gli aggiornamenti sono gli Observer.
Immaginiamo un’applicazione e-commerce. Quando un ordine viene confermato, potrebbe essere necessario:
- inviare un’e-mail al cliente;
- registrare l’evento in un sistema di log;
- aggiornare le statistiche di vendita;
- inviare una notifica a un servizio esterno.
Senza una struttura adeguata, la classe Ordine rischia di contenere troppo codice e di dipendere direttamente da molti servizi. Con il Pattern Observer, invece, l’ordine comunica soltanto che qualcosa è accaduto. Saranno gli osservatori registrati a decidere come reagire.
Questo approccio favorisce il principio di separazione delle responsabilità: ogni classe si occupa di un compito preciso e aggiungere una nuova reazione a un evento non richiede di modificare la logica principale dell’ordine.
Codice completo
<?php
declare(strict_types=1);
/**
* Contratto per tutti gli oggetti che vogliono ricevere notifiche.
*/
interface OrderObserver
{
public function update(Order $order): void;
}
/**
* Classe osservata: rappresenta un ordine.
*/
final class Order
{
/**
* @var OrderObserver[]
*/
private array $observers = [];
private string $status = ´pending´;
public function __construct(
private readonly int $id,
private readonly string $customerEmail,
private readonly float $total
) {
}
public function attach(OrderObserver $observer): void
{
$this->observers[] = $observer;
}
public function detach(OrderObserver $observer): void
{
foreach ($this->observers as $key => $registeredObserver) {
if ($registeredObserver === $observer) {
unset($this->observers[$key]);
}
}
// Ricrea gli indici dell´array dopo la rimozione.
$this->observers = array_values($this->observers);
}
public function confirm(): void
{
if ($this->status === ´confirmed´) {
return;
}
$this->status = ´confirmed´;
$this->notifyObservers();
}
private function notifyObservers(): void
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function getId(): int
{
return $this->id;
}
public function getCustomerEmail(): string
{
return $this->customerEmail;
}
public function getTotal(): float
{
return $this->total;
}
public function getStatus(): string
{
return $this->status;
}
}
/**
* Observer che invia una conferma via e-mail.
*/
final class EmailNotificationObserver implements OrderObserver
{
public function update(Order $order): void
{
echo sprintf(
"E-mail inviata a %s per l´ordine #%d.%s",
$order->getCustomerEmail(),
$order->getId(),
PHP_EOL
);
}
}
/**
* Observer che registra l´evento in un log.
*/
final class OrderLogObserver implements OrderObserver
{
public function update(Order $order): void
{
echo sprintf(
"Log: ordine #%d confermato per un totale di %.2f euro.%s",
$order->getId(),
$order->getTotal(),
PHP_EOL
);
}
}
/**
* Observer che aggiorna le statistiche.
*/
final class SalesStatisticsObserver implements OrderObserver
{
private float $totalSales = 0.0;
public function update(Order $order): void
{
$this->totalSales += $order->getTotal();
echo sprintf(
"Statistiche aggiornate. Totale vendite: %.2f euro.%s",
$this->totalSales,
PHP_EOL
);
}
public function getTotalSales(): float
{
return $this->totalSales;
}
}
// Creazione dell´ordine.
$order = new Order(
id: 1001,
customerEmail: ´[email protected]´,
total: 149.90
);
// Registrazione degli osservatori.
$emailObserver = new EmailNotificationObserver();
$logObserver = new OrderLogObserver();
$statisticsObserver = new SalesStatisticsObserver();
$order->attach($emailObserver);
$order->attach($logObserver);
$order->attach($statisticsObserver);
// Tutti gli observer ricevono la notifica.
$order->confirm();
// Un observer può essere rimosso quando non è più necessario.
$order->detach($logObserver); Spiegazione
Il contratto dell’Observer
L’interfaccia OrderObserver definisce il metodo update(). Qualsiasi classe che implementa questa interfaccia può essere collegata a un ordine e ricevere una notifica.
Il metodo riceve l’oggetto Order, così l’observer può leggere tutte le informazioni necessarie, come identificativo, indirizzo e-mail, importo e stato.
La classe Subject
La classe Order mantiene un array privato chiamato $observers. Il metodo attach() aggiunge un osservatore, mentre detach() lo rimuove.
Il metodo confirm() modifica lo stato dell’ordine e richiama notifyObservers(). Quest’ultimo attraversa tutti gli observer registrati e invoca il loro metodo update().
La classe Order non sa se sta inviando un’e-mail, scrivendo un log o aggiornando statistiche. Conosce soltanto il contratto OrderObserver. Questo riduce il coupling, cioè la dipendenza diretta tra le classi.
Aggiungere nuovi comportamenti
Per inviare una notifica a un sistema esterno è sufficiente creare una nuova classe:
final class ExternalWebhookObserver implements OrderObserver
{
public function update(Order $order): void
{
echo "Webhook inviato per l´ordine #{$order->getId()}." . PHP_EOL;
}
} La classe Order non deve essere modificata. È sufficiente registrare il nuovo observer con $order->attach(new ExternalWebhookObserver()).
Best practice
- Usa interfacce: il Subject dovrebbe dipendere da un contratto, non da implementazioni concrete.
- Mantieni piccoli gli observer: ogni observer dovrebbe occuparsi di una sola reazione all’evento.
- Evita logica complessa nel Subject: la classe osservata deve notificare, non svolgere tutti i lavori successivi.
- Gestisci gli errori: in produzione, un problema nell’invio dell’e-mail non dovrebbe necessariamente impedire la registrazione dell’ordine.
- Valuta l’asincronia: se le operazioni sono lente, gli eventi possono essere inseriti in una coda per essere elaborati da un worker.
- Evita duplicati: puoi impedire che lo stesso observer venga registrato più volte, usando identificativi o controlli espliciti.
- Scrivi test indipendenti: ogni observer può essere testato separatamente senza creare un intero flusso d’ordine.
In applicazioni più grandi, può essere utile sostituire il termine “observer” con “listener” e introdurre un vero Event Dispatcher. Il concetto resta simile: un evento viene pubblicato e più componenti reagiscono senza essere strettamente collegati tra loro.
Riepilogo
Il Pattern Observer permette a un oggetto di notificare più componenti quando cambia il proprio stato o quando si verifica un evento. In PHP, un’interfaccia definisce il contratto degli observer, mentre il Subject conserva gli oggetti registrati e richiama il metodo di aggiornamento.
Questo pattern è particolarmente adatto per sistemi di notifiche, audit, aggiornamento delle statistiche, integrazioni con API e gestione degli eventi di dominio. Il vantaggio principale è la possibilità di aggiungere nuovi comportamenti senza modificare la classe principale.
