Observer Pattern
Definerer en one-to-many afhængighed mellem objekter, så når ét objekt ændrer tilstand, notificeres alle afhængige objekter automatisk.
Om Patterned
Observer Pattern er et fundamentalt behavioral design pattern introduceret af Gang of Four i 1994, men konceptet stammer fra Model-View-Controller (MVC) arkitekturen udviklet i Smalltalk i 1970'erne. Patterned etablerer en subscription mekanisme hvor observere kan registrere sig til at modtage notifikationer fra et subject når dets tilstand ændres, hvilket fremmer loose coupling mellem komponenter. Dette er essentielt i event-driven arkitekturer, reactive programming og GUI frameworks hvor multiple views skal opdateres når underliggende data ændres. PHP inkluderer built-in SplObserver og SplSubject interfaces i Standard PHP Library, men mange moderne implementationer bruger event dispatcher systemer som i Symfony eller Laravel. Observer Pattern er grundlaget for mange moderne programmeringsparadigmer inkluderende reactive extensions og pub/sub systemer.
Key Points
- ✓Etablerer one-to-many relationship mellem subject og observers
- ✓Observers registrerer sig (subscribe) hos subject
- ✓Subject notificerer automatisk alle observers ved state changes
- ✓Fremmer loose coupling - subject kender ikke observernes detaljer
- ✓PHP's SPL tilbyder SplObserver og SplSubject interfaces
- ✓Også kendt som Publish-Subscribe pattern
- ✓Basis for event-driven programming
- ✓Observers kan tilføjes eller fjernes dynamisk runtime
- ✓Subject sender typisk sig selv eller event data til observers
- ✓Kan føre til memory leaks hvis observers ikke unsubscribes
- ✓Performance overhead ved mange observers
- ✓Notifikationsordre er typisk ikke garanteret
Kode Eksempel
<?php
declare(strict_types=1);
namespace App\Order;
use SplObserver;
use SplSubject;
use SplObjectStorage;
enum OrderStatus: string
{
case PENDING = 'pending';
case PAID = 'paid';
case SHIPPED = 'shipped';
case DELIVERED = 'delivered';
case CANCELLED = 'cancelled';
}
/**
* Subject - Order der kan observeres
*/
final class Order implements SplSubject
{
private SplObjectStorage $observers;
private OrderStatus $status;
public function __construct(
private readonly string $orderId,
private readonly float $total,
private readonly string $customerEmail
) {
$this->observers = new SplObjectStorage();
$this->status = OrderStatus::PENDING;
}
public function attach(SplObserver $observer): void
{
$this->observers->attach($observer);
}
public function detach(SplObserver $observer): void
{
$this->observers->detach($observer);
}
public function notify(): void
{
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
public function updateStatus(OrderStatus $newStatus): void
{
$this->status = $newStatus;
echo "[Order {$this->orderId}] Status changed to: {$newStatus->value}\n";
$this->notify();
}
public function getOrderId(): string
{
return $this->orderId;
}
public function getStatus(): OrderStatus
{
return $this->status;
}
public function getTotal(): float
{
return $this->total;
}
public function getCustomerEmail(): string
{
return $this->customerEmail;
}
}
/**
* Observer - Email notifikationer
*/
final readonly class EmailNotificationObserver implements SplObserver
{
public function update(SplSubject $subject): void
{
if (!$subject instanceof Order) {
return;
}
$email = $subject->getCustomerEmail();
$status = $subject->getStatus()->value;
echo " [Email] Sending notification to {$email} about status: {$status}\n";
}
}
/**
* Observer - Inventory opdatering
*/
final readonly class InventoryObserver implements SplObserver
{
public function update(SplSubject $subject): void
{
if (!$subject instanceof Order) {
return;
}
if ($subject->getStatus() === OrderStatus::PAID) {
echo " [Inventory] Reserving items for order {$subject->getOrderId()}\n";
} elseif ($subject->getStatus() === OrderStatus::CANCELLED) {
echo " [Inventory] Releasing reserved items for order {$subject->getOrderId()}\n";
}
}
}
/**
* Observer - Analytics tracking
*/
final readonly class AnalyticsObserver implements SplObserver
{
public function update(SplSubject $subject): void
{
if (!$subject instanceof Order) {
return;
}
$orderId = $subject->getOrderId();
$status = $subject->getStatus()->value;
$total = $subject->getTotal();
echo " [Analytics] Tracking: Order {$orderId} -> {$status} (DKK {$total})\n";
}
}
/**
* Observer - Shipping service
*/
final readonly class ShippingObserver implements SplObserver
{
public function update(SplSubject $subject): void
{
if (!$subject instanceof Order) {
return;
}
if ($subject->getStatus() === OrderStatus::PAID) {
echo " [Shipping] Creating shipping label for order {$subject->getOrderId()}\n";
}
}
}
// Brug af Observer Pattern
$order = new Order('ORD-12345', 599.00, 'customer@example.com');
// Attach observers
$order->attach(new EmailNotificationObserver());
$order->attach(new InventoryObserver());
$order->attach(new AnalyticsObserver());
$order->attach(new ShippingObserver());
echo "\n=== Order Status Updates ===\n\n";
// Når status ændres, notificeres alle observers automatisk
$order->updateStatus(OrderStatus::PAID);
echo "\n";
$order->updateStatus(OrderStatus::SHIPPED);
echo "\n";
$order->updateStatus(OrderStatus::DELIVERED);
Fordele
- +Loose coupling mellem subject og observers
- +Open/Closed Principle - nye observers kan tilføjes uden at ændre subject
- +Dynamic relationships - observers kan tilføjes/fjernes runtime
- +Broadcast kommunikation til multiple objekter simultant
- +Fremmer Single Responsibility - hver observer har én specifik opgave
- +Grundlag for event-driven og reactive programming
Ulemper
- −Observers notificeres i arbitrær rækkefølge (ikke deterministisk)
- −Memory leaks hvis observers ikke detaches korrekt
- −Performance overhead ved mange observers
- −Debugging kan være svært da flow ikke er eksplicit
Hvornår bruges det?
- •Event systems - notificer multiple komponenter om system events
- •Order processing - opdater inventory, shipping, email ved order changes
- •User notifications - send notifikationer via multiple kanaler (email, SMS, push)
- •Cache invalidation - invalider caches når data opdateres
- •Logging systems - log events til multiple destinations
- •Real-time dashboards - opdater UI komponenter ved data changes
Best Practices
- ✓Brug PHP's SPL interfaces (SplObserver, SplSubject) for standardisering
- ✓Ensure observers unsubscribe (detach) når de ikke længere skal notificeres
- ✓Overvej event objects i stedet for at sende hele subject
- ✓Implementer error handling i observers så én fejl ikke stopper alle
- ✓Dokumenter hvilke events subject kan udsende
- ✓Brug weak references hvis observers holder subject references
- ✓Overvej async notification for performance-kritiske systemer
- ✓Test observers isoleret fra subject
Quick Info
- • Mediator Pattern - centraliserer kommunikation mellem objekter
- • Event Dispatcher - modernere approach til observer pattern
- • Publish-Subscribe - variant med message broker mellem publisher og subscribers