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 (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
eventochdelegateför enklare fall.
Nackdelar
- Subscribers notifieras i obestämd ordning.
Av Victor Hernandez från Bytebase.se