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

RollAnsvar
PublisherHåller listan av observatörer och tillhandahåller subscribe/unsubscribe.
Subscriber InterfaceDefinierar notifieringsgränssnittet.
Concrete SubscriberImplementerar 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