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 (TypeScript)
Scenariot: En Editor publicerar händelser (open, save) och prenumeranter reagerar automatiskt.
// ── Subscriber Interface ───────────────────────────────────
interface EventListener {
update(eventType: string, fileName: string): void;
}
// ── Publisher ─────────────────────────────────────────────
class EventManager {
private listeners: Map<string, EventListener[]> = new Map();
constructor(...operations: string[]) {
for (const op of operations) {
this.listeners.set(op, []);
}
}
subscribe(eventType: string, listener: EventListener): void {
this.listeners.get(eventType)?.push(listener);
}
unsubscribe(eventType: string, listener: EventListener): void {
const list = this.listeners.get(eventType) ?? [];
const idx = list.indexOf(listener);
if (idx !== -1) list.splice(idx, 1);
}
notify(eventType: string, fileName: string): void {
for (const listener of this.listeners.get(eventType) ?? []) {
listener.update(eventType, fileName);
}
}
}
class Editor {
public events = new EventManager('open', 'save');
private currentFile = '';
openFile(path: string): void {
this.currentFile = path;
this.events.notify('open', path);
}
saveFile(): void {
this.events.notify('save', this.currentFile);
}
}
// ── Concrete Subscribers ──────────────────────────────────
class EmailNotificationListener implements EventListener {
constructor(private email: string) {}
update(eventType: string, fileName: string): void {
console.log(`E-post till ${this.email}: händelse '${eventType}' på fil ${fileName}`);
}
}
class LoggingListener implements EventListener {
constructor(private logFile: string) {}
update(eventType: string, fileName: string): void {
console.log(`Logg [${this.logFile}]: ${eventType} - ${fileName}`);
}
}
// ── Klientkod ─────────────────────────────────────────────
const 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.
- Svårt att spåra händelsekedjan i komplexa system.
Av Victor Hernandez från Bytebase.se