← Tilbage til patterns

Observer Pattern

Definerer en one-to-many afhængighed mellem objekter, så når ét objekt ændrer tilstand, notificeres alle afhængige objekter automatisk.

BehavioralMellem

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

Kategori
Behavioral
Sværhedsgrad
Mellem
Relaterede Patterns
  • Mediator Pattern - centraliserer kommunikation mellem objekter
  • Event Dispatcher - modernere approach til observer pattern
  • Publish-Subscribe - variant med message broker mellem publisher og subscribers