Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Wzorzec Observer i Pub/Sub

W centrum kontroli Parku Jurajskiego, Robert Muldoon obserwuje dziesiątki monitorów jednocześnie. "Problem polega na tym," wyjaśnia, "że każdy system - ogrodzenia, kamery, czujniki ruchu - musi natychmiast reagować na zmiany statusu dinozaura. Ale nie mogą ciągle sprawdzać (pollować) stan bazy danych!" Rozwiązaniem jest wzorzec Observer - mechanizm, w którym obiekty automatycznie powiadamiają zainteresowanych o zmianach.

Problem: Ścisłe powiązanie komponentów

Bez wzorca Observer, komponenty muszą się bezpośrednio znać i wywoływać:

1// Problem: system alarmowy zna WSZYSTKIE inne systemy
2function onDinosaurEscaped(dinosaur) {
3  // Musi ręcznie wywołać każdy system
4  fenceSystem.activateEmergency(dinosaur);
5  cameraSystem.trackDinosaur(dinosaur);
6  alarmSystem.soundAlert(dinosaur);
7  gateSystem.lockAllGates();
8  evacuationSystem.startEvacuation();
9  // Co jeśli dodamy nowy system? Trzeba zmodyfikować tę funkcję!
10}

Każdy nowy system wymaga modyfikacji istniejącego kodu. To łamie zasadę otwarte-zamknięte (Open-Closed Principle).

Wzorzec Observer - podstawy

We wzorcu Observer mamy dwa typy obiektów:

  • Subject (Observable) - obiekt, który emituje zdarzenia (powiadamia o zmianach)
  • Observer - obiekt, który nasłuchuje na zdarzenia i reaguje
1// Prosty EventEmitter - serce wzorca Observer
2class EventEmitter {
3  constructor() {
4    this.listeners = {};
5  }
6
7  // Rejestracja obserwatora (subskrypcja)
8  on(event, callback) {
9    if (!this.listeners[event]) {
10      this.listeners[event] = [];
11    }
12    this.listeners[event].push(callback);
13    return this; // Umożliwia chaining
14  }
15
16  // Emisja zdarzenia (powiadomienie obserwatorów)
17  emit(event, ...args) {
18    const callbacks = this.listeners[event];
19    if (callbacks) {
20      callbacks.forEach(callback => callback(...args));
21    }
22    return this;
23  }
24
25  // Usunięcie obserwatora
26  off(event, callback) {
27    if (this.listeners[event]) {
28      this.listeners[event] = this.listeners[event].filter(
29        cb => cb !== callback
30      );
31    }
32    return this;
33  }
34
35  // Jednorazowa subskrypcja
36  once(event, callback) {
37    const wrapper = (...args) => {
38      callback(...args);
39      this.off(event, wrapper);
40    };
41    this.on(event, wrapper);
42    return this;
43  }
44}

Zastosowanie w Parku Jurajskim

1// System monitoringu dinozaurów oparty na EventEmitter
2class DinosaurMonitor extends EventEmitter {
3  constructor() {
4    super();
5    this.dinosaurs = new Map();
6  }
7
8  registerDinosaur(id, data) {
9    this.dinosaurs.set(id, { ...data, status: "contained" });
10    this.emit("dinosaur:registered", { id, ...data });
11  }
12
13  updateStatus(id, newStatus) {
14    const dino = this.dinosaurs.get(id);
15    if (!dino) return;
16
17    const oldStatus = dino.status;
18    dino.status = newStatus;
19
20    this.emit("status:changed", { id, name: dino.name, oldStatus, newStatus });
21
22    // Emitujemy specjalne zdarzenie dla ucieczki
23    if (newStatus === "escaped") {
24      this.emit("dinosaur:escaped", { id, name: dino.name, species: dino.species });
25    }
26  }
27}
28
29// Tworzenie systemu
30const monitor = new DinosaurMonitor();
31
32// Rejestracja obserwatorów - każdy system sam się podłącza
33monitor.on("dinosaur:escaped", (data) => {
34  console.log(`ALARM OGRODZENIA: ${data.name} uciekł! Aktywuję elektryczne ogrodzenie.`);
35});
36
37monitor.on("dinosaur:escaped", (data) => {
38  console.log(`ALARM KAMERY: Śledzę ${data.name} (${data.species}) na kamerach.`);
39});
40
41monitor.on("dinosaur:escaped", (data) => {
42  console.log(`ALARM EWAKUACJI: Rozpoczynam ewakuację z powodu ucieczki ${data.name}!`);
43});
44
45monitor.on("status:changed", (data) => {
46  console.log(`LOG: ${data.name} zmienił status z "${data.oldStatus}" na "${data.newStatus}"`);
47});
48
49// Rejestracja i symulacja
50monitor.registerDinosaur("TREX-001", { name: "Rexy", species: "T-Rex" });
51monitor.registerDinosaur("RAPTOR-01", { name: "Blue", species: "Velociraptor" });
52
53// Symulacja ucieczki
54monitor.updateStatus("TREX-001", "escaped");
55// LOG: Rexy zmienił status z "contained" na "escaped"
56// ALARM OGRODZENIA: Rexy uciekł! Aktywuję elektryczne ogrodzenie.
57// ALARM KAMERY: Śledzę Rexy (T-Rex) na kamerach.
58// ALARM EWAKUACJI: Rozpoczynam ewakuację z powodu ucieczki Rexy!

Wzorzec Publish/Subscribe (Pub/Sub)

Pub/Sub to rozszerzenie wzorca Observer, gdzie komunikacja odbywa się przez kanały/tematy (topics) zamiast bezpośrednich referencji:

1class PubSub {
2  constructor() {
3    this.topics = {};
4    this.subscriptionId = 0;
5  }
6
7  // Subskrypcja tematu
8  subscribe(topic, callback) {
9    if (!this.topics[topic]) {
10      this.topics[topic] = {};
11    }
12
13    const id = ++this.subscriptionId;
14    this.topics[topic][id] = callback;
15
16    // Zwracamy funkcję do anulowania subskrypcji
17    return () => {
18      delete this.topics[topic][id];
19    };
20  }
21
22  // Publikacja wiadomości
23  publish(topic, data) {
24    if (!this.topics[topic]) return;
25
26    Object.values(this.topics[topic]).forEach(callback => {
27      callback(data);
28    });
29  }
30}
31
32// Centralny system komunikacji parku
33const parkBus = new PubSub();
34
35// Systemy subskrybują tematy, które ich interesują
36const unsubFence = parkBus.subscribe("security:alert", (data) => {
37  console.log(`Ogrodzenie: Otrzymano alert - ${data.message}`);
38});
39
40parkBus.subscribe("security:alert", (data) => {
41  console.log(`Centrum dowodzenia: Alert poziomu ${data.level} - ${data.message}`);
42});
43
44parkBus.subscribe("feeding:schedule", (data) => {
45  console.log(`Kuchnia: Przygotować ${data.food} dla ${data.dinosaur}`);
46});
47
48// Publikacja zdarzeń - nadawca nie wie kto nasłuchuje
49parkBus.publish("security:alert", {
50  level: "HIGH",
51  message: "Wykryto ruch przy ogrodzeniu sektora C"
52});
53
54parkBus.publish("feeding:schedule", {
55  dinosaur: "Rexy",
56  food: "200kg wołowiny",
57  time: "14:00"
58});
59
60// Anulowanie subskrypcji
61unsubFence(); // Ogrodzenie przestaje nasłuchiwać

Praktyczny przykład: System powiadomień

1class ParkNotificationSystem extends EventEmitter {
2  constructor() {
3    super();
4    this.history = [];
5  }
6
7  notify(channel, message, priority = "normal") {
8    const notification = {
9      channel,
10      message,
11      priority,
12      timestamp: new Date().toISOString()
13    };
14
15    this.history.push(notification);
16    this.emit(channel, notification);
17    this.emit("*", notification); // Wildcard - wszystkie powiadomienia
18  }
19
20  getHistory(channel) {
21    if (channel) {
22      return this.history.filter(n => n.channel === channel);
23    }
24    return [...this.history];
25  }
26}
27
28const notifications = new ParkNotificationSystem();
29
30// Panel administratora - nasłuchuje wszystko
31notifications.on("*", (n) => {
32  console.log(`[ADMIN] [${n.priority.toUpperCase()}] ${n.channel}: ${n.message}`);
33});
34
35// System bezpieczeństwa - tylko alerty
36notifications.on("security", (n) => {
37  if (n.priority === "critical") {
38    console.log(`CZERWONY ALARM: ${n.message}`);
39  }
40});
41
42// Logowanie
43notifications.on("maintenance", (n) => {
44  console.log(`Konserwacja: ${n.message}`);
45});
46
47// Użycie
48notifications.notify("security", "Awaria kamery w sektorze B", "normal");
49notifications.notify("security", "T-Rex przerwał ogrodzenie!", "critical");
50notifications.notify("maintenance", "Wymiana żarówek w laboratorium", "low");
51
52console.log("Historia alertów:", notifications.getHistory("security"));

Observer vs Pub/Sub - kiedy używać?

| Cecha | Observer | Pub/Sub | |-------|----------|---------| | Powiązanie | Subject zna obserwatorów | Nadawca i odbiorca się nie znają | | Komunikacja | Bezpośrednia | Przez kanał/temat | | Użyj gdy | Jeden obiekt powiadamia kilku | Wiele obiektów komunikuje się luźno | | Przykład | Formularz powiadamia walidatory | Mikroserwisy wymieniają wiadomości |

Podsumowanie

Muldoon podsumowuje: "Wzorzec Observer to jak system alarmowy parku - nie musisz ciągle sprawdzać każdego czujnika. Czujniki same Cię powiadomią:"

  1. Observer - obiekt (Subject) powiadamia zarejestrowanych obserwatorów o zmianach
  2. EventEmitter - implementacja Observera z metodami
    on()
    ,
    emit()
    ,
    off()
  3. Pub/Sub - luźniejsze powiązanie przez kanały/tematy, nadawca nie zna odbiorców
  4. Odsprzęganie - komponenty nie muszą się bezpośrednio znać, komunikują się przez zdarzenia

Te wzorce są fundamentem wielu bibliotek i frameworków - od Node.js (EventEmitter) przez React (state management) po architektury mikroserwisowe.

Vai a CodeWorlds