Observer (Megfigyelő)
Kategória: Viselkedési minta (Behavioral Pattern)
Lényege és célja
Az Observer minta egy "egy a többhöz" (one-to-many) függőséget hoz létre az objektumok között. Lényege, hogy ha egy objektum (a Megfigyelt / Subject) állapota megváltozik, arról minden tőle függő objektum (a Megfigyelők / Observers) automatikusan értesítést kap és frissül.
Gyakran hivatkozunk rá Pub-Sub (Publisher-Subscriber) vagy eseményvezérelt (Event-driven) modellként is.
Miért zseniális ez a minta?
A minta legnagyobb ereje a laza csatolásban (loose coupling) rejlik. A Megfigyelt (pl. a tőzsdei központ) egyáltalán nem ismeri a konkrét megfigyelőket (Mobil App, Email). Csak egy listát vezet róluk, és annyit tud, hogy mindegyikük megvalósít egy közös Observer interfészt (vagyis van egy update() metódusuk). Ennek köszönhetően: * Bármikor, futásidőben adhatunk hozzá új megfigyelőket. * Bármikor leiratkozhat egy megfigyelő. * Ha írunk egy teljesen új klienst (pl. egy TelegramBotAlert osztályt), a StockTicker forráskódjához egyetlen karakter erejéig sem kell hozzányúlnunk!
Analógia a való életből
Gondolj egy YouTube csatornára! A videós (Subject) nem küld egyesével e-mailt a nézőknek, ha új videója van. A nézők feliratkoznak (Subscribe / addObserver) a csatornára a YouTube rendszerében. Amikor kikerül az új videó, a rendszer egy ciklussal végigmegy a listán, és mindenkinek küld egy értesítést (notify). Ha valaki megunja a tartalmat, leiratkozik (removeObserver), és a továbbiakban nem kap üzenetet.
Mikor használjuk?
- Amikor egy objektum állapotának megváltozása egy vagy több másik objektum frissítését teszi szükségessé, de nem tudjuk (vagy nem akarjuk tudni) előre, hogy pontosan mik ezek az objektumok.
- Grafikus felületeknél (GUI): amikor egy gombnyomásra (Event) több különböző panelnek is reagálnia kell a képernyőn.
- Moduláris, aszinkron és reaktív rendszerek építésénél.
Mermaid Diagram
A diagramon látható a minta tipikus felépítése: a StockTicker fenntartja az Observer interfészt implementáló objektumok listáját, és állapotváltozás (setPrice) esetén végighívja rajtuk az update() metódust.
classDiagram
class Subject {
<<interface>>
+addObserver(o: Observer) void
+removeObserver(o: Observer) void
+notifyObservers() void
}
class StockTicker {
-observers: List~Observer~
-price: double
+addObserver(o: Observer) void
+removeObserver(o: Observer) void
+notifyObservers() void
+setPrice(stockName, price) void
}
class Observer {
<<interface>>
+update(stockName: String, price: double) void
}
class MobileAppDisplay {
+update(stockName, price) void
}
class EmailAlert {
+update(stockName, price) void
}
class Client
StockTicker ..|> Subject : "implementálja"
MobileAppDisplay ..|> Observer : "implementálja"
EmailAlert ..|> Observer : "implementálja"
%% A Subject "HAS-A" (kompozíció) kapcsolatban áll az Observers listával
StockTicker o--> Observer : "lista a feliratkozókról"
Client --> StockTicker : "állapotot változtat"
Client --> MobileAppDisplay : "példányosít" Forráskód
package behavioral.observer;
import java.util.ArrayList;
import java.util.List;
interface Observer {
void update(String stockName, double price);
}
class MobileAppDisplay implements Observer {
@Override
public void update(String stockName, double price) {
System.out.printf("Mobil app riasztás: Az %s új ára %s USD\n", stockName, price);
}
}
class EmailAlert implements Observer {
@Override
public void update(String stockName, double price) {
System.out.printf("Email küldve: %s - %s\n", stockName, price);
}
}
class StockTicker {
private final List<Observer> observers = new ArrayList<>();
void addObserver(Observer o) {
observers.add(o);
}
void removeObserver(Observer o) {
observers.remove(o);
}
private void notifyObservers(String stockName, double price) {
observers.forEach(it -> it.update(stockName, price));
}
void setPrice(String stockName, double price) {
// todo
notifyObservers(stockName, price);
}
}
class Main {
static void main() {
var ticker = new StockTicker();
var emailObserver = new EmailAlert();
var mobileAppObserver = new MobileAppDisplay();
ticker.addObserver(emailObserver);
ticker.addObserver(mobileAppObserver);
ticker.setPrice("AAPL", 150.0);
ticker.removeObserver(emailObserver);
ticker.setPrice("AAPL", 250.0);
}
}