Wzór obserwatora; wiedząc * co * się zmieniło?

10

Stworzyłem dwie abstrakcyjne klasy Subject i Observer, które definiują klasyczny interfejs wzorców Observer. Czerpię z nich implementację wzorca Observer. Obserwator może wyglądać następująco:

void MyClass::Update(Subject *subject)
{
    if(subject == myService_)
    {
        DoSomething();
    }
    else if(subject == myOtherService_)
    {
        DoSomethingElse();
    }
}

To dobrze i mówi mi, kto coś zmienił. Jednak nie mówi mi, co się zmieniło. Czasami jest to w porządku, ponieważ po prostu zapytam podmiot o najnowsze dane, ale innym razem muszę wiedzieć, co dokładnie zmieniło się w temacie. Zauważyłem w Javie, że mają zarówno metodę replaceObservers (), jak i metodę noticeObservers (Object arg) , aby przypuszczalnie określać szczegóły zmian.

W moim przypadku muszę wiedzieć, czy jedno z kilku różnych działań miało miejsce w tym temacie, a jeśli jest to określone działanie, znać liczbę całkowitą związaną z tym działaniem.

Więc moje pytania to:

  1. jaki jest sposób C ++, aby przekazać ogólny argument (tak jak Java)?
  2. Czy Observer jest nawet najlepszym wzorem? Może jakiś system wydarzeń?

AKTUALIZACJA

Znalazłem ten artykuł, który mówi o szablonowaniu wzorca Observer: Implementowanie wzorca Temat / Obserwator za pomocą szablonów . To sprawiło, że zastanawiałem się, czy możesz szablonować argument.

Znalazłem pytanie o przepełnienie stosu, które mówi o szablonowaniu argumentu: Wzorzec obserwatora oparty na szablonie - czy powinienem użyć static_cast lub dynamic_cast . Wydaje się jednak, że PO ma problem, na który nikt nie odpowiedział.

Inną rzeczą, którą mogłem zrobić, to zmienić metodę Update, aby pobierała obiekt EventArg jak w:

void MyClass::Update(Subject *subject, EventArg arg)
{
  ...

A następnie utwórz podklasy EventArg dla konkretnych danych argumentów, a następnie przypuszczam, że wrzuciłem ją z powrotem do konkretnej podklasy w ramach metody aktualizacji.

AKTUALIZACJA 2

Znalazłem także artykuł na temat tworzenia asynchronicznej struktury c ++ opartej na komunikatach; część 2 omawiającej mające Temat komunikować szczegóły dotyczące tego, co zmieniło.

Teraz poważnie rozważam użycie Boost.Signals . Używanie własnego wzorca obserwatora miało sens, gdy było proste, ale szablonowanie typu i argument zaczyna się komplikować. I może potrzebuję bezpieczeństwa wątku Boost.Signals2.

AKTUALIZACJA 3

Znalazłem też kilka interesujących artykułów na temat wzorca obserwatora:

Generalizing Observer autorstwa Herb Sutter

Implementowanie wzorca obserwatora w C ++ - część 1

Doświadczenia związane z wdrażaniem wzorca projektowego obserwatora (część 2)

Doświadczenia związane z wdrażaniem wzorca projektowego obserwatora (część 3)

Jednak zmieniłem moją implementację na używanie Boost.Signals, które, choć być może nieco rozdęte dla moich celów, z powodzeniem działa. I prawdopodobnie wszelkie obawy o wzdęcie lub prędkość są nieistotne.

Użytkownik
źródło
Pytania w ciele tak naprawdę nie pasują do twojego tytułu. Co naprawdę stanowi sedno pytania?
Nicole,
@Reneesis: Obecnie używam wzorca Observer jak w przykładzie kodu na początku mojego postu. W przypadku kodu, nad którym obecnie pracuję, okazuje się, że muszę dokładnie wiedzieć, co się zmieniło, aby odpowiednio zareagować. Moja obecna implementacja wzorca obserwatora (który jest standardowy) nie zawiera tych informacji. Moje pytanie brzmi: jak najlepiej uzyskać informacje o tym, co się zmieniło.
Użytkownik
@Reneesis: Oto wątek na forum zadający podobne pytanie do mojego: gamedev.net/topic/497105-observer-pattern
Użytkownik
Aktualizacja reklamy 2: Zdecydowanie popieram używanie Boost.Signals. Jest to o wiele wygodniejsze niż tworzenie własnych. Istnieje również libsigc ++, jeśli chcesz czegoś lżejszego tylko do tego zadania (Boost.Signals używa dużo kodu z reszty Boosta); nie jest jednak wątkowo bezpieczny.
Jan Hudec,
Jeśli chodzi o problemy z prędkością: nie wiem dokładnie, jak szybki jest Boost.Signals, ale to tylko niepokój, gdy masz dużo wydarzeń latających wokół ...
Max

Odpowiedzi:

4

Czy C ++ lub Java, zgłoszenie do obserwatora może przyjść wraz z informacjami o co się zmieniło. Te same metody replaceObservers (Object arg) mogą być również używane w C ++.

Ogólnie rzecz biorąc, problem pozostanie taki, że może istnieć wiele podmiotów wysyłających do jednego lub wielu obserwatorów, a zatem class argnie mogą być zakodowane na stałe.

Zwykle najlepszym sposobem jest utworzenie arg w postaci ogólnej wiadomości / tokenów, które tworzą ten sam typ danych dla różnych klas, ale wartości różnią się dla różnych obserwowanych klas. Alternatywnie, jeśli wszystkie takie wartości powiadomień są wyprowadzane z klasy w pewnej klasie bazowej, która jest wspólna dla wszystkich.

W przypadku wzorca obserwatora ważne jest, aby typ danych Arg nie był zakodowany na stałe między obserwowanym a obserwatorem - w przeciwnym razie jest to sprzężenie, które utrudnia ewolucję.

EDYCJA
Jeśli chcesz, aby obserwator nie tylko obserwował, ale także musiał wykonać wiele innych zadań w oparciu o to, co się wtedy zmieniło , możesz także sprawdzić wzorzec Odwiedzającego . We wzorcu gościa obserwator / odwiedzający wywołuje zmodyfikowany obiekt, dzięki czemu może nie tylko wiedzieć, czym jest modyfikacja, ale może faktycznie nad nią pracować

Dipan Mehta
źródło
5
Jeżeli obserwator interpretuje argument, tam jest sprzęgło, bez względu na to, w jaki sposób ukryć typ. W rzeczywistości powiedziałbym, że to bardziej trudne do ewoluować jeśli przejdą Object( void *, boost::anylub coś podobnie ogólne) niż jeśli przejdą szczególnego rodzaju, ponieważ z typem konkretnego zobaczysz w czasie kompilacji, że coś się zmieniło, gdy z typu rodzajowego to skompiluje się i przestanie działać, ponieważ obserwator nie będzie mógł pracować z faktycznie przekazanymi danymi.
Jan Hudec
@JanHudec: Zgadzam się z tym, ale czy to oznacza, że ​​tworzysz określoną jednorazową podklasę Obserwator / Temat dla każdego argumentu (tj. Dla każdego przypadku użycia)?
Użytkownik
@JanHudec: także sprzężenie jest tylko jednym sposobem. Badany nie ma pojęcia o obserwatorach. Tak, obserwator wie o temacie, ale czy nie tak działa wzór obserwatora?
Użytkownik
1
@ Użytkownik: Tak, tworzę specjalny interfejs dla każdego tematu i każdy obserwator implementuje interfejsy tematów, które musi obserwować. Cóż, wszystkie języki, których używam, mają powiązane wskaźniki metod w języku lub frameworku (C # delegaci, C ++ 11 std::functionBoost boost::function, Gtk + GClosure, metody związane z pythonami itp.), Więc po prostu definiuję metody z odpowiednimi podpisami i pytam system o utworzenie rzeczywistych obserwator. Sprzężenie jest rzeczywiście tylko jednym sposobem, podmiot definiuje interfejs dla obserwatorów, ale nie ma pojęcia o ich implementacji.
Jan Hudec,
0

Istnieje kilka sposobów wysłania ogólnego argumentu zdarzenia „Java Like” w C ++.

1) Zadeklaruj argument zdarzenia jako nieważny * i umieść go w odpowiedniej klasie w module obsługi zdarzeń.

2) Zadeklaruj argument zdarzenia jako wskaźnik / odwołanie do nowej klasy / interfejsu, takiego jak (w odpowiedzi na twój przykład)

class GenericEventArgument
{
  virtual bool didAction1Happen() = 0;
  virtual int getActionInteger() = 0;
};

I niech rzeczywiste klasy argumentów zdarzeń pochodzą z tej klasy.

Jeśli chodzi o twoje pytanie dotyczące obserwatora opartego na szablonie, sprawdź

http://www.codeproject.com/Articles/3267/Implementing-a-Subject-Observer-pattern-with-templ

Gonen I.
źródło
-1

Nie wiem, czy to kanoniczna nazwa, ale z moich dawnych czasów Smalltalk pamiętam termin „aspekt”, aby określić, co zmieniło się na obserwowalnym.

Nie jest to tak skomplikowane, jak pomysł na EventArg (i jego podklasowanie); po prostu przekazuje ciąg znaków (może być nawet liczbą całkowitą) z obserwowalnego do obserwatora.

Plus: istnieją tylko dwie proste metody ( update(observable)iupdateAspect(observable, aspect)

Minus: obserwator może poprosić obserwowalnego o dodatkowe informacje (tj. „Liczbę całkowitą”)

virtualnobi
źródło