Патерн observer на java

На прохання читачів чергову статтю про патернах проектування присвячу паттерну Observer (Спостерігач). Цей Шаблон проектування також відомий під іменами Dependents (Підлеглі) і Publisher-Subscriber (Видавець-Підписчик).

Реалізація даного патерну використовується для спостереження за станом об'єктів в системі. Якщо стан об'єктів змінюється в процесі їх життєвого циклу, то Спостерігач оповіщає інші частини системи про ці події.

У даній статті я спробую якомога простіше і зрозуміліше розповісти про це паттерне і привести приклад програмного коду на Java, що реалізує Observer.

В яких випадках використовується Спостерігач?

  • Якщо один об'єкт повинен передавати повідомлення іншим об'єктам, але при цьому він не може або не повинен знати про їх внутрішній устрій;
  • У разі якщо при зміні одного об'єкта необхідно змінювати інші об'єкти;
  • Для запобігання сильних зв'язків між об'єктами системи;
  • Для спостереження за станом певних об'єктів системи;

Спостережуваний (observable) об'єкт, а точніше суб'єкт або subject, повинен надавати інтерфейс для реєстрації і дерегістраціі спостерігачів (listeners).

Ну а самі спостерігачі повинні як мінімум мати відкритим методом через який і буде відбуватися сповіщення про зміну стану суб'єкта. Цей метод часто називають notify.

Так як спостерігачів може бути досить багато, для спрощення роботи з ними можна використовувати колекцію (collection of observers). Приблизно такий код я використовую в цьому випадку:

Клас EmptyObserver може бути корисний у разі якщо у Спостерігача досить велика кількість notify методів. Тоді використовуючи анонімні класи можна з легкістю створювати необхідні нам "вузькоспеціалізовані" спостерігачі (ті, у яких реалізовано обмежена кількість методів) на льоту:

Далі, екземпляр класу Observers поміщається в суб'єкт за яким має вестися спостереження. У всіх місцях коду, де відбувається цікавить нас дію з класом-суб'єктом, додаємо відповідний виклик notify методу у колекції спостерігачів:

Класичний приклад використання патерну Observer - це класи з пакета Java Swing. У Swing патерн Спостерігач використовується для організації слабкою взаємозалежності між моделлю і графічними об'єктами. Методи notify викликаються при зміні значень властивостей моделі та передають інформацію в віджети.

До речі, дуже рекомендую всім подивитися вихідний код Java Swing. Це дійсно дуже якісно спроектована бібліотека.

Як щодо підтримки багатопоточності і синхронізації списку нотіфіціруемих об'єктів?

Для воізбежаніе Дедлоков можна викликати нотифікацію, залочений такий список ...

1) У кожного нотіфіціруемого об'єкта є лічильник посилань, який при додаванні в список збільшується а при видаленні зменшується;

2) Робота зі списком контролюється будь-яким примітивом синхронізації;

3) При нотифікації необхідно скопіювати список під локом, при цьому збільшивши всіх об'єктах лічильники посилань (найзручніше використовувати список аутопоінтеров, по типу ATL :: CComPtr), прибрати лок і нотифікувати слухачів по копії списку, після чого зменшити лічильники посилань.

судячи з Вашому прикладу не бачу різниці між listeners and callback-ми? Або це одне і теж? прояніте ласка цей момент або прімерчік на відмінності накидайте будь ласка!

Ярик: Головна відмінність listener від callback в тому, що callback може бути один (або жодного, я на Delphi пишу, тому nil). Listener'ов ж може бути скільки завгодно. Правда, є одне застереження. Listenerи, якщо їх багато, можуть не знати один про одного. А в разі callback можна смастрячіть ланцюжок callback (аналогічно будь-якому WinAPI, такому як SetWindowsHookEx, або SetCliboardViewer), щоб "нагорі" ієрархії висів останній зареєстрований callback, при цьому він буде "знати" про раніше устеновленном (попередньому) callback і зможе його викликати. Як на мене, так ідея callback більш "рідна", ніж listener. Хоча б тому, що я можу чітко контролювати: я хочу бути викликаний останнім, першим, або в залежності від умови не викликати наступні callback ( "з'їсти" подія). У випадку з listener зробити нічого подібного не можна. Зате реєстрація подій в моделі listener набагато простіше: ні тимчасових змінних, ні проблем з моделями виклику stdcall, cdecl та інше, коротше - скукота.

Схожі статті