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 (C# / .NET)

Scenariot: En Editor publicerar händelser (open, save) och prenumeranter reagerar automatiskt. I .NET kan detta även göras med inbyggda event och delegate, men vi använder mönstret explicit här.

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

public interface IEventListener
{
    void Update(string eventType, string fileName);
}

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

public class EventManager
{
    private readonly Dictionary<string, List<IEventListener>> _listeners = new();

    public EventManager(params string[] operations)
    {
        foreach (string op in operations)
            _listeners[op] = new List<IEventListener>();
    }

    public void Subscribe(string eventType, IEventListener listener) =>
        _listeners[eventType].Add(listener);

    public void Unsubscribe(string eventType, IEventListener listener) =>
        _listeners[eventType].Remove(listener);

    public void Notify(string eventType, string fileName)
    {
        foreach (IEventListener listener in _listeners.GetValueOrDefault(eventType) ?? [])
            listener.Update(eventType, fileName);
    }
}

public class Editor
{
    public EventManager Events { get; } = new("open", "save");
    private string _currentFile = string.Empty;

    public void OpenFile(string path)
    {
        _currentFile = path;
        Events.Notify("open", path);
    }

    public void SaveFile() => Events.Notify("save", _currentFile);
}

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

public class EmailNotificationListener(string email) : IEventListener
{
    public void Update(string eventType, string fileName) =>
        Console.WriteLine($"E-post till {email}: händelse '{eventType}' på fil {fileName}");
}

public class LoggingListener(string logFile) : IEventListener
{
    public void Update(string eventType, string fileName) =>
        Console.WriteLine($"Logg [{logFile}]: {eventType} - {fileName}");
}

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

Editor editor = new();

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.
  • .NET har dessutom inbyggt stöd via event och delegate för enklare fall.

Nackdelar

  • Subscribers notifieras i obestämd ordning.

Av Victor Hernandez från Bytebase.se