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
| Roll | Ansvar |
|---|---|
| Publisher | Håller listan av observatörer och tillhandahåller subscribe/unsubscribe. |
| Subscriber Interface | Definierar notifieringsgränssnittet. |
| Concrete Subscriber | Implementerar 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