Kihagyás

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);

    }
}