March
2008
По просьбам читателей очередную статью о паттернах проектирования посвящу паттерну 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. Это действительно очень качественно спроектированная библиотека.
13 Comments »
RSS feed for comments on this post.
Спасибо! Доходчиво и понятно объяснил и показал *THUMBS_UP*
Еще вопрос: что вот это за структура такая?
…
class Observers extends ArrayList {
public void notifyObjectCreated(Object obj) {
for (Iterator iter = (Iterator) iterator(); iter.hasNext();)
…
Очень похоже на шаблоны в С++, но в то же время что-то другое.. .может какуюнить статейку на эту тему или хотя бы где можно об этом почитать..
Спасибо
угловые скобки и их содержимое не пропечатались…
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();
}
Это параметризируемые типы (generic).
Скоро напишу статью на эту тему.
в методе notifyObjectModified наверное должно быть не iter.next().objectCreated(), а iter.next().objectModified()
У вас в коде возможно ошибка в методе notifyObjectModified. Возможно нужно вызвать iter.next().objectModified() вместо iter.next().objectCreated()?
Да, спасибо, исправил.
Написанный кода выполняет следущую фукцию: мы вводим текст в окошко ввода(используется класс генерации случайных чисел Random), и в окошке вывода получаем измененный текст, то есть слова мешаются. Вопрос: Возможно и разумно в этом случае использовать OBSERVER?
Если я правильно понимаю, Вы используете некоторую библиотеку (например Swing) для построения GUI и перемешивание слов должно осуществлятся в реальном времени после нажатия клавиш.
Если так, то нужно использовать Listener (это и есть observer), а точнее добавить listener полю вывода перемешанных слов, который будет слушать событие нажатия кнопки.
Циклы, использующие iterator, красивее выглядят через foreach.
П.С. После препроцессора получится то же самое, но выглядит красивее.
Зачем такая сложная конструкция
for (Iterator iter = (Iterator) iterator(); iter.hasNext();)
iter.next().objectCreated();
}
Можно заменить на
for(T observer: Observers.this){
observer.objectCreated(obj);
}
Как насчет поддержки многопоточности и синхронизации списка нотифицируемых объектов?
Для воизбежания дедлоков нельзя вызывать нотификацию, залочив такой список…
Простейшая реализация:
1) У каждого нотифицируемого объекта имеется счетчик ссылок, который при добавлении в список увеличивается а при удалении уменьшается;
2) Работа со списком контролируется каким-либо примитивом синхронизации;
3) При нотификации необходимо скопировать список под локом, при этом увеличив всем объектам счетчики ссылок (удобней всего использовать список аутопоинтеров, по типу ATL::CComPtr), убрать лок и нотифицировать слушателей по копии списка, после чего уменьшить счетчики ссылок.