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 är intresserad av en specifik produkt som snart ska komma i lager.
Kunden kan besöka butiken varje dag för att kontrollera lagret — men de flesta besöken är förgäves. Alternativt kan butiken skicka e-post till alla kunder varje gång något nytt kommer in — men det irriterar kunder som inte är intresserade.
Observer-mönstret löser detta: prenumerera bara på det du faktiskt bryr dig om.
Lösning
Observer-mönstret lägger till en prenumerationsmekanism i objektet som har ett intressant tillstånd (Publisher). Individuella objekt kan prenumerera på eller avsluta prenumerationen när de vill.
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 kunna 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 (Java)
Scenariot: En Editor publicerar händelser (open, save) och prenumeranter som EmailNotificationListener och LoggingListener reagerar automatiskt.
import java.util.*;
// ── Subscriber Interface ───────────────────────────────────
public interface EventListener {
void update(String eventType, String fileName);
}
// ── Publisher ─────────────────────────────────────────────
public class EventManager {
private Map<String, List<EventListener>> listeners = new HashMap<>();
public EventManager(String... operations) {
for (String op : operations) {
listeners.put(op, new ArrayList<>());
}
}
public void subscribe(String eventType, EventListener listener) {
listeners.get(eventType).add(listener);
}
public void unsubscribe(String eventType, EventListener listener) {
listeners.get(eventType).remove(listener);
}
public void notify(String eventType, String fileName) {
for (EventListener listener : listeners.get(eventType)) {
listener.update(eventType, fileName);
}
}
}
// Context som använder EventManager
public class Editor {
public EventManager events;
private String currentFile;
public Editor() {
this.events = new EventManager("open", "save");
}
public void openFile(String path) {
this.currentFile = path;
events.notify("open", path);
}
public void saveFile() {
events.notify("save", currentFile);
}
}
// ── Concrete Subscribers ──────────────────────────────────
public class EmailNotificationListener implements EventListener {
private String email;
public EmailNotificationListener(String email) {
this.email = email;
}
@Override
public void update(String eventType, String fileName) {
System.out.println("E-post till " + email + ": händelse '" + eventType + "' på fil " + fileName);
}
}
public class LoggingListener implements EventListener {
private String logFile;
public LoggingListener(String logFile) {
this.logFile = logFile;
}
@Override
public void update(String eventType, String fileName) {
System.out.println("Logg [" + logFile + "]: " + eventType + " - " + fileName);
}
}
// ── Klientkod ─────────────────────────────────────────────
public class Application {
public static void main(String[] args) {
Editor editor = new Editor();
// Prenumerera på händelser
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.
- Om publisher-kedjan är lång kan det vara svårt att spåra vad som triggar vad.
Av Victor Hernandez från Bytebase.se