Wzorzec obserwator C++/Qt

Często się zdarza że programiści napotykają problem we współczesnych programu posiadające interfejs użytkownika (GUI), muszą reagować na zmiany danych w kilku różnych miejscach jednocześnie. Do sytuacji takiej może dojść wtedy gdy pracujemy na tych samych danych, które możemy edytować w kilku miejscach jednocześnie np. wykresy obrazujące takie same dane. Zmiana wartości na jednym wykresie implikuje zmianę wartości na pozostałych wykresach.

W przypadku kiedy mamy do czynienia z kilkoma obiektami możemy wykorzystać mechanizm sygnałów i slotów w Qt, jednak jeżeli jednak liczba obserwowanych obiektów, które się zmieniają nie jest zdefiniowana lub dotyczy znacznej grupy obiektów możemy zbudować osobną klasę zwaną Obserwatorem mającą na celu powiadamianie obiektów o występujących zmianach.

Pomimo że istnieje wiele różnych implementacji tego samego wzorca mają one elementy wspólne takie jak:

Wszystkie z obiektów można podzielić na dwie osobne grupy wydawców i klas pełniących rolę obserwatorów
Każda z implementacji zakłada że jest jeden obiekt, które informuje inne obiekty o zmianach
Mechanizm wysyłania informacji do pozostałych klas jest zdefiniowany w całości w klasie bazowej wydawcy.

Zalety i wady

Zalety:

Luźna zależność między obiektem obserwującym i obserwowanym. Ponieważ nie wiedzą one wiele o sobie nawzajem, mogą być niezależnie rozszerzane i rozbudowywane bez wpływu na drugą stronę.
Relacja między obiektem obserwowanym a obserwatorem tworzona jest podczas wykonywania programu i może być dynamicznie zmieniana.
Możliwość zablokowania klientowi drogi do bezpośredniego korzystania ze złożonego systemu, jeśli jest to konieczne.

Wady:

Obserwatorzy nie znają innych obserwatorów, co w pewnych sytuacjach może wywołać trudne do znalezienia skutki uboczne.
Zastosowanie

Wzorzec Obserwatora sprawdza się wszędzie tam, gdzie stan jednego obiektu uzależniony jest od stanu drugiego obiektu.

Implementacja

//Obserwator:
class Obserwator {
public:
    virtual void update() = 0;
};
//Obserwowany:

class Obserwowany {
protected:
    std::list <Obserwator*> obserwatorzy;
public:
    void dodaj(Obserwator *o) {
        obserwatorzy.push_back (o);
    }
    void usun(Obserwator *o) {
        obserwatorzy.remove (o);
    }
 
    void powiadom () {
        std::list<Obserwator *>::iterator it;
        for (it = obserwatorzy.begin(); it != obserwatorzy.end(); it++) {
            (*it)->update ();
        }
    }
};

Obserwowany Konkretny:

Dziedziczenie wielokrotne dostępne w C++ jest często używane w wzorcu Obserwatora

class Miod {
protected:
    int ilosc;
public:
    Miod (int q) {
        ilosc = q;
    }
 
    int pobierzIlosc () {
        return ilosc;
    }
 
    void ustawIlosc (int q) {
        ilosc = q;
    }
};
 
class ObserwowanyMiod : public Obserwowany, public Miod {
    std::string stan;
public:
    ObserwowanyMiod (int q) : Miod(q) {
    }
 
    std::string pobierzStan () {
        return stan;
    }
 
    void ustawStan (const std::string& s) {
        stan = s;
        std::cout << "Stan: " << stan << std::endl;
        powiadom();
    }
};

Obserwator Konkretny:

class ObserwatorMis : public Obserwator {
protected:
    int misId;
    ObserwowanyMiod *miod;
public:
    ObserwatorMis (ObserwowanyMiod *h, int id) {
        miod = h;
        misId = id;
    }
    void update () {
        std::string stan = miod->pobierzStan();
        if (!stan.compare ("Sa ludzie blisko miodu")) {
            std::cout << "Mis" << misId << ": Czekam w ukryciu" << std::endl;
        } else if (!stan.compare ("Nie ma ludzi blisko miodu")) {
            std::cout << "Mis" << misId << ": Kradne miod" << std::endl;
            miod->ustawIlosc(miod->pobierzIlosc()-1);
        } else if (!stan.compare ("Ida ludzie")) {
            std::cout << "Mis" << misId << ": Uciekam" << std::endl;
        }
    }
};

Przykład użycia:

int main() {
    ObserwowanyMiod miod (5);
    Obserwator *mis1 = new ObserwatorMis(&miod, 1);
    Obserwator *mis2 = new ObserwatorMis(&miod, 2);
 
    miod.dodaj(mis1);
    miod.dodaj(mis2);
 
    std::cout << "Ilosc miodu: " << miod.pobierzIlosc() << std::endl;
 
    miod.ustawStan("Sa ludzie blisko miodu");
    miod.ustawStan("Nie ma ludzi blisko miodu");
    miod.ustawStan("Ida ludzie");
 
    std::cout << "Ilosc miodu: " << miod.pobierzIlosc() << std::endl;
    delete mis1;
    delete mis2;
    return 0;
}