Observer (Observatör)

Introduktion

Observer är ett beteendemönster som låter dig definiera en prenumerationsmekanism för att notifiera flera objekt om händelser som inträffar i objektet de observerar.


Problem

Tänk dig att du har en Butik och en Kund. Kunden vill veta när en produkt kommer i lager, men det är slöseri att kolla varje dag — och butiken vill inte spamma alla kunder. Observer löser detta med en opt-in prenumerationsmekanism.

Lösning

Observer-mönstret lägger till en prenumerationsmekanism i objektet med ett intressant tillstånd (Publisher). Individuella objekt väljer själva att prenumerera och tar emot notifieringar automatiskt.

När ska Observer användas?

  • När en ändring i ett objekt kräver att andra objekt ändras, och du inte vet i förväg hur många.
  • När ett objekt ska notifiera andra utan att göra antaganden om vilka de är.
  • För händelsestyrd arkitektur och reaktiva system.

Struktur

RollAnsvar
PublisherHåller listan av observatörer och tillhandahåller subscribe/unsubscribe.
Subscriber InterfaceDefinierar notifieringsgränssnittet.
Concrete SubscriberImplementerar reaktionen på notifieringen.

Exempel — Filhanteringssystem (PHP)

Scenariot: En Editor publicerar händelser (open, save) och prenumeranter reagerar automatiskt.

<?php

// ── Subscriber Interface ───────────────────────────────────

interface EventListener
{
    public function update(string $eventType, string $fileName): void;
}

// ── Publisher ─────────────────────────────────────────────

class EventManager
{
    private array $listeners = [];

    public function __construct(string ...$operations)
    {
        foreach ($operations as $op) {
            $this->listeners[$op] = [];
        }
    }

    public function subscribe(string $eventType, EventListener $listener): void
    {
        $this->listeners[$eventType][] = $listener;
    }

    public function unsubscribe(string $eventType, EventListener $listener): void
    {
        $this->listeners[$eventType] = array_filter(
            $this->listeners[$eventType],
            fn($l) => $l !== $listener
        );
    }

    public function notify(string $eventType, string $fileName): void
    {
        foreach ($this->listeners[$eventType] ?? [] as $listener) {
            $listener->update($eventType, $fileName);
        }
    }
}

class Editor
{
    public EventManager $events;
    private string $currentFile = '';

    public function __construct()
    {
        $this->events = new EventManager('open', 'save');
    }

    public function openFile(string $path): void
    {
        $this->currentFile = $path;
        $this->events->notify('open', $path);
    }

    public function saveFile(): void
    {
        $this->events->notify('save', $this->currentFile);
    }
}

// ── Concrete Subscribers ──────────────────────────────────

class EmailNotificationListener implements EventListener
{
    public function __construct(private string $email) {}

    public function update(string $eventType, string $fileName): void
    {
        echo "E-post till {$this->email}: händelse '{$eventType}' på fil {$fileName}\n";
    }
}

class LoggingListener implements EventListener
{
    public function __construct(private string $logFile) {}

    public function update(string $eventType, string $fileName): void
    {
        echo "Logg [{$this->logFile}]: {$eventType} - {$fileName}\n";
    }
}

// ── Klientkod ─────────────────────────────────────────────

$editor = new Editor();

$editor->events->subscribe('open', new LoggingListener('log.txt'));
$editor->events->subscribe('save', new EmailNotificationListener('admin@bytebase.se'));
$editor->events->subscribe('save', new LoggingListener('log.txt'));

$editor->openFile('rapport.docx');
$editor->saveFile();

Output:

Logg [log.txt]: open - rapport.docx
E-post till admin@bytebase.se: händelse 'save' på fil rapport.docx
Logg [log.txt]: save - rapport.docx

Fördelar

  • Open/Closed Principle — Lägg till nya subscribers utan att ändra publisherns kod.
  • Prenumerationsrelationer kan etableras och avslutas vid runtime.

Nackdelar

  • Subscribers notifieras i obestämd ordning.

Av Victor Hernandez från Bytebase.se