Javenue logo

Javenue

Программирование на Java

Информационные технологии

Паттерн Observer на Java - Наблюдатель

По просьбам читателей очередную статью о паттернах проектирования посвящу паттерну Observer (Наблюдатель). Этот Шаблон проектирования также известен под именами Dependents (Подчиненные) и Publisher-Subscriber (Издатель-Подписчик).

Реализация данного паттерна используется для наблюдения за состоянием объектов в системе. Если состояние объектов изменяется в процессе их жизненного цикла, то Наблюдатель оповещает другие части системы об этих событиях.

В данной статье я попытаюсь как можно проще и понятнее рассказать об этом паттерне и привести пример программного кода на Java, реализующего Observer.

В каких случаях используется Наблюдатель?

  • Если один объект должен передавать сообщения другим объектам, но при этом он не может или не должен знать об их внутреннем устройстве;
  • В случае если при изменении одного объекта необходимо изменять другие объекты;
  • Для предотвращения сильных связей между объектами системы;
  • Для наблюдения за состоянием определенных объектов системы;

Наблюдаемый (observable) объект, а точнее субъект или subject, должен предоставлять интерфейс для регистрации и дерегистрации наблюдателей (listeners).

Ну а сами наблюдатели должны как минимум обладать открытым методом через который и будет происходить оповещение об изменении состояния субъекта. Этот метод часто называют notify.

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

    public interface Observer {
        void objectCreated(Object obj);
        void objectModified(Object obj);
    }

    class EmptyObserver implements Observer {
        public void objectCreated(Object obj) { }
        public void objectModified(Object obj) { }
    }

    class Observers<T extends Observer> extends ArrayList<T> {
        public void notifyObjectCreated(Object obj) { 
            for (Iterator<T> iter = (Iterator<T>) iterator(); iter.hasNext();)
                iter.next().objectCreated(obj);
        }
        public void notifyObjectModified(Object obj) { 
            for (Iterator<T> iter = (Iterator<T>) iterator(); iter.hasNext();)
                iter.next().objectModified(obj);
        }
    }

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

  Observers observers = new Observers();
  observers.add(new EmptyObserver() {
    public void objectCreated(Object obj) { /* реализация */ }
  });

Далее, экземпляр класса Observers помещается в субъект за которым должно вестись наблюдение. Во всех местах кода, где происходит интересующее нас действие с классом-субъектом, добавляем соответствующий вызов notify метода у коллекции наблюдателей:

    public class Subject {
        Observers observers = new Observers();

        private Object field;

        public void setField(Object o) {
            field = o;
            observers.notifyObjectModified(this);
        }
    }

Кажется, основы осветил. Нюансы предлагаю обсуждать в комментариях.

Классический пример использования паттерна Observer - это классы из пакета Java Swing. В Swing паттерн Наблюдатель используется для организации слабой взаимозависимости между моделью и графическими объектами. Методы notify вызываются при изменении значений свойств модели и передают информацию в виджеты.

Кстати, очень рекомендую всем посмотреть исходный код Java Swing. Это действительно очень качественно спроектированная библиотека.



Комментариев: 3

  Выйти

  * для публикации комментариев нужно  

Ryadovoy:

Как насчет поддержки многопоточности и синхронизации списка нотифицируемых объектов?

Для воизбежания дедлоков нельзя вызывать нотификацию, залочив такой список…

Простейшая реализация:

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

2) Работа со списком контролируется каким-либо примитивом синхронизации;

3) При нотификации необходимо скопировать список под локом, при этом увеличив всем объектам счетчики ссылок (удобней всего использовать список аутопоинтеров, по типу ATL::CComPtr), убрать лок и нотифицировать слушателей по копии списка, после чего уменьшить счетчики ссылок.

Ярик:

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

Python SmiSoft:

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