CodeLAB
на главную карта сайта обратная связь
каталог | задачи | паттерны | исходники | стат | форумы | ссылки
 гость
искать в
Главная >> Каталог задач >> Паттерны >> Поведения >> Наблюдатель (Observer)

<< назад
распечатать обсудить >>


Наблюдатель (Observer)
реализации: java, количество: 2

Aвтор: this
Дата: 05.10.2007
Просмотров: 78652
Рейтинг: 0/0,0(0)
+
реализации(исходники) +добавить

Имя

«Паттерн
Observer» оригинал codelab.ru источник codelab.ru
Наблюдатель - паттерн поведения объектов, устанавливающий систему оповещения объектами своих соседей в процессе их деятельности. codelab.ru источник оригинал codelab.ru

Известен также под именами: Dependents (подчиненные), Publish-Subscribe (издатель-подписчик). оригинал codelab.ru codelab.ru источник

Условия, Задача, Назначение

Очень часто в процессе функционирования и взаимодействия объектов системы нужно оповещать других участников по завершении какой-нибудь значимой операции. codelab.ru оригинал codelab.ru источник
Конечно же, можно в каждый такой класс, производящий значимые действия добавлять обращение к этим всем другим заинтересованным объектам, но этот способ довольно губителен: мало того, что таким образом мы «хардкодим» (дублируем) как попало новые связи между объектами (система становится все менее и менее гибкой) – это еще и в разы затруднит последующую модификацию каких либо участников, т.к. необходимо будет перекомпилировать этот код обращения ко всем заинтересованных субъектам. codelab.ru источник codelab.ru оригинал
В этом случае очень подошло бы иметь такую структуру, в которой каждый участник, если он заинтересован в каких-либо событиях системы, мог бы самостоятельно «подписаться» на эти изменния независимо от других заинтересованных участникам – и, таким образом, получая уведомления об этих событиях – выполнять требуемые ответные действия. codelab.ru codelab.ru оригинал источник
В результате – не создается лишних связей: есть источник значимых действий, есть заинтересованные в фактах выполнения этих действий субъекты, никак не связанный друг с другом, количество которых при этом – также неограниченно. codelab.ru codelab.ru источник оригинал
Паттерн-наблюдатель описывает именно такой подход: codelab.ru оригинал источник codelab.ru
определяет зависимость типа «один ко многим» между объектами таким образом, что при изменении состояния одного объекта все зависящие от него оповещаются об этом и автоматически обновляются.
codelab.ru источник оригинал codelab.ru

Мотивация

В результате разбиения системы на множество совместно работающих классов появляется необходимость поддерживать согласованное состояние взаимосвязанных объектов. Но не хотелось бы, чтобы за согласованность надо было платить жесткой связанностью классов, так как это значительно уменьшает возможности повторного использования. оригинал codelab.ru codelab.ru источник
Например, во многих библиотеках для построения графических интерфейсов пользователя презентационные аспекты интерфейса отделены от данных приложения. С классами, описывающими данные (Модели) и их представление (View, шаблоны), можно работать автономно. Электронная таблица и диаграмма, представляющие одни и те же данные, не имеют информации друг о друге, поэтому вы вправе использовать их по отдельности. Но ведут они себя так, как будто «знают» друг о друге. Когда пользователь работает с таблицей, все изменения немедленно отражаются на диаграмме, и наоборот: codelab.ru оригинал codelab.ru источник

источник codelab.ru оригинал codelab.ru

При таком поведении подразумевается, что и электронная таблица, и диаграмма зависят от данных объекта и поэтому должны уведомляться о любых изменениях в его состоянии. И нет никаких причин, ограничивающих количество зависимых объектов; для работы с одними и теми же данными может существовать любое число пользовательских интерфейсов. оригинал источник codelab.ru codelab.ru
Паттерн наблюдатель описывает, как устанавливать такие отношения. Ключевыми объектами в нем являются субъект и наблюдатель. У субъекта может быть сколько угодно зависимых от него наблюдателей. Все наблюдатели уведомляются об изменениях в состоянии субъекта. Получив уведомление, наблюдатель обновляет у субъекта данные, чтобы синхронизировать с ним свое состояние. codelab.ru источник оригинал codelab.ru
Такого рода взаимодействие часто называется отношением издатель-подписчик. Субъект издает или публикует уведомления и рассылает их, даже не имея информации о том, какие объекты являются подписчиками. На получение уведомлений может подписаться неограниченное количество наблюдателей. codelab.ru оригинал источник codelab.ru
codelab.ru codelab.ru оригинал источник

Признаки применения, использования паттерна Наблюдатель (Observer)

Используйте паттерн наблюдатель в следующих ситуациях: оригинал codelab.ru codelab.ru источник
  1. Когда у абстракции есть два аспекта, один из которых зависит от другого.
    Инкапсуляции этих аспектов в разные объекты позволяют изменять и повторно использовать их независимо. codelab.ru источник codelab.ru оригинал
  2. Когда при модификации одного объекта требуется изменить и другие, но неизвестно, сколько именно объектов нужно изменить. codelab.ru источник codelab.ru оригинал
  3. Когда один объект должен оповещать других, не делая предположений, не владея информацией об уведомляемых объектах. Другими словами, вы не хотите, чтобы объекты были тесно связаны между собой. оригинал codelab.ru codelab.ru источник

Решение

codelab.ru источник codelab.ru оригинал

оригинал codelab.ru источник codelab.ru

Участники паттерна Наблюдатель (Observer)

codelab.ru codelab.ru оригинал источник
  1. Subject – субъект.
    Регистрирует своих наблюдателей. За субъектом может «следить» любое число наблюдателей.
    Предоставляет интерфейс для регистрации и, соотвественно, отписки наблюдателей. codelab.ru codelab.ru оригинал источник
  2. Observer – наблюдатель.
    Определяет интерфейс для уведомления подписчисчиков, т.е. объектов, заинтересованных в изменениях субъекта. источник codelab.ru codelab.ru оригинал
  3. ConcreteSubject - конкретный субъект.
    Сохраняет состояние, представляющее интерес для любого конкретного наблюдателя ConcreteObserver.
    Посылает информацию своим наблюдателям, когда происходит изменение. codelab.ru оригинал источник codelab.ru
  4. ConcreteObserver - конкретный наблюдатель.
    Хранит ссылку на объект класса ConcreteSubject (для того чтобы потом обращаться к нему за синхронизацией данных).
    Сохраняет данные, которые должны быть согласованы с данными субъекта.
    Реализует интерфейс обновления, определенный в классе Observer, чтобы «быть уведомленным» о изменениях ConcreteSubject-а. codelab.ru источник codelab.ru оригинал

Схема использования паттерна Наблюдатель (Observer)

Объект ConcreteSubject уведомляет своих наблюдателей о любом изменении, которое могло бы привести к рассогласованности состояний наблюдателей и субъекта. После получения от конкретного субъекта уведомления об изменении объект ConcreteObserver может запросить у субъекта дополнительную информацию, которую использует для того, чтобы оказаться в состоянии, согласованном с состоянием субъекта. источник codelab.ru оригинал codelab.ru

На диаграмме взаимодействий показаны отношения между субъектом и двумя наблюдателями. оригинал codelab.ru codelab.ru источник

codelab.ru источник codelab.ru оригинал

Объект Observer, который инициирует запрос на изменение, откладывает свое обновление до получения уведомления от субъекта. Операция Notify не всегда вызывается субъектом. Ее может вызвать и наблюдатель, и посторонний объект. В следующем разделе обсуждаются часто встречающиеся варианты. оригинал codelab.ru codelab.ru источник

Вопросы, касающиеся реализации паттерна Наблюдатель (Observer)

При реализации механизма подписчиков возникает множество аспектов: источник codelab.ru codelab.ru оригинал
  1. Отображение субъектов на наблюдателей.
    С помощью этого простейшего способа субъект может отследить всех наблюдателей (ConcreteObserver-ов), которым он должен посылать уведомления, то есть хранить на них явные ссылки (т.е. традиционный). Однако при наличии большого числа субъектов и всего нескольких наблюдателей это может оказаться накладно. Один из возможных компромиссов в пользу экономии памяти за счет времени состоит в том, чтобы использовать ассоциативный массив (например, хэш-таблицу) для хранения отображения между субъектами и наблюдателями. Тогда субъект, у которого нет наблюдателей, не будет зря расходовать память. С другой стороны, при таком подходе увеличивается время поиска наблюдателей. источник codelab.ru codelab.ru оригинал
  2. Наблюдение более чем за одним субъектом.
    Иногда наблюдатель (Observer) может зависеть более чем от одного субъекта. Например, у электронной таблицы бывает более одного источника данных. В таких случаях необходимо расширить интерфейс метода Update, чтобы наблюдатель мог «узнать», какой субъект прислал уведомление. Субъект может просто передать себя в качестве параметра операции Update, тем самым, сообщая наблюдателю, что именно нужно обследовать. источник codelab.ru codelab.ru оригинал
  3. Инициатор обновления.
    Чтобы сохранить согласованность, субъект и его наблюдатели полагаются на механизм уведомлений. Но какой именно объект вызывает операцию Notify для инициирования обновления? Есть два варианта:
    + операции класса Subject, изменившие состояние, вызывают Notifу для уведомления об этом изменении. Преимущество такого подхода в том, что клиентам не надо помнить о необходимости вызывать операцию Notify субъекта. Недостаток же заключается в следующем: при выполнении каждой из нескольких последовательных операций будут производиться лишние обновления, что может стать причиной неэффективной работы программы.
    + ответственность за своевременный вызов Notify возлагается на клиента. Преимущество: клиент может отложить инициирование обновления до завершения серии изменений, исключив тем самым ненужные промежуточные обновления. Недостаток: у клиентов появляется дополнительная обязанность. Это увеличивает вероятность ошибок, поскольку клиент может забыть вызвать Notify.
    В то же время можно предусмотреть комбинированный вариант: там где недостатки одного подхода начинают начинают преобладать – начинает использоваться другой. codelab.ru источник оригинал codelab.ru
  4. «Висячие» ссылки на удаленные субъекты.
    Удаление субъекта не должно приводить к появлению висячих ссылок у наблюдателей. Избежать этого можно, например, поручив субъекту уведомлять все свои наблюдатели о своем удалении, чтобы они могли уничтожить хранимые у себя ссылки. В общем случае простое удаление наблюдателей не годится, так как на них могут ссылаться другие объекты и под их наблюдением могут находиться другие субъекты. codelab.ru codelab.ru оригинал источник
  5. Гарантии непротиворечивости состояния субъекта перед отправкой уведомления.
    Важно быть уверенным, что перед вызовом операции Notify состояние субъекта непротиворечиво, поскольку в процессе обновления собственного состояния наблюдатели будут опрашивать состояние субъекта.
    Правило непротиворечивости очень легко нарушить, если операции одного из подклассов класса Subject вызывают унаследованные операции. Например, в следующем фрагменте уведомление отправляется, когда состояние субъекта уже противоречиво:
     позднее обновление состояния [java]  ссылка
    1. package observer;
    2.  
    3. public class MySubject {
    4.  
    5. public void Operation(int newValue) {
    6. BaseClassSubject.Operation(); /* Отправить уведомление */
    7.  
    8. /* Обновление состояние подкласса...
    9. * - что на самом деле уже слишком поздно
    10. */
    11. _myInstVar = newValue;
    12. }
    13. }


    Избежать этой ловушки можно, отправляя уведомления из шаблонных методов абстрактного класса Subject. Определите примитивную операцию, замещаемую в подклассах, и обратитесь к Notify, используя последнюю операцию в шаблонном методе. В таком случае существует гарантия, что состояние объекта непротиворечиво, если операции Subject замещены в подклассах:
     уведомления через шаблонные методы Subject-а [java]  ссылка
    1. package observer;
    2.  
    3. public class Text {
    4.  
    5. public void Cut(TextRange r) {
    6. ReplaceRange(r);
    7. Notify();
    8. }
    9. }


    Желательно фиксировать, какие операции класса Subject инициируют обновления. оригинал источник codelab.ru codelab.ru
  6. Зависимость протокола.
    Как избежать зависимости протокола обновления от наблюдателя: модели вытягивания и проталкивания. В реализациях паттерна наблюдатель субъект довольно часто транслирует всем подписчикам дополнительную информацию о характере изменения. Она передается в виде аргумента операции Update, и объем ее меняется в широких диапазонах.
    На одном полюсе находится так называемая модель проталкивания (push model), когда субъект посылает наблюдателям детальную информацию об изменении независимо от того, нужно ли им это. На другом - модель вытягивания (pull model), когда субъект не посылает ничего, кроме минимального уведомления, а наблюдатели сами запрашивают детали позднее.
    В модели вытягивания подчеркивается неинформированность субъекта о своих наблюдателях, а в модели проталкивания предполагается, что субъект владеет определенной информацией о потребностях наблюдателей. В случае применения модели проталкивания степень повторного их использования может снизиться, так как классы Subject предполагают о классах Observer, которые не всегда могут быть верны. С другой стороны, модель вытягивания может оказаться неэффективной, ибо наблюдателям без помощи субъекта необходимо выяснять, что изменилось. codelab.ru codelab.ru оригинал источник
  7. Явное специфицирование представляющих интерес модификаций.
    Эффективность обновления можно повысить, расширив интерфейс регистрации субъекта, т.е. предоставив возможность при регистрации наблюдателя указать, какие события его интересуют. Когда событие происходит, субъект информирует лишь тех наблюдателей, которые проявили к нему интерес.
    Чтобы получать конкретное событие, наблюдатели присоединяются к своим субъектам следующим образом:
     присоединение наблюдателя к субъекту [C++]  ссылка
    1. void Subject::Attach(Observer*, Aspects interest);


    - где interest определяет представляющее интерес событие. В момент посылки уведомления субъект передает своим наблюдателям изменившийся аспект в виде параметра операции Update. Например:
     посылка уведомления [C++]  ссылка
    1. void Observer::Update(Subject*, Aspects interest);

    codelab.ru источник codelab.ru оригинал
  8. Инкапсуляция сложной семантики обновления.
    Если отношения зависимости между субъектами и наблюдателями становятся особенно сложными, то может потребоваться объект, инкапсулирующий эти отношения. Будем называть его ChangeManager (менеджер изменений). Он служит для минимизации объема работы, необходимой для того чтобы наблюдатели смогли отразить изменения субъекта. Например, если некоторая операция влечет за собой изменения в нескольких независимых субъектах, то хотелось бы, чтобы наблюдатели уведомлялись после того, как будут модифицированы все субъекты, дабы не ставить в известность одного и того же наблюдателя по несколько раз.
    У класса ChangeManager есть три обязанности:
    - строить отображение между субъектом и его наблюдателями и предоставлять интерфейс для поддержания отображения в актуальном состоянии. Это освобождает субъектов от необходимости хранить ссылки на своих наблюдателей и наоборот.
    - определять конкретную стратегию обновления (совокупность действий).
    - обновлять всех зависимых наблюдателей по запросу от субъекта.
    На следующей диаграмме представлена простая реализация паттерна наблюдатель с использованием менеджера изменений ChangeManager. Имеется два специализированных менеджера. SimpleСhangeManager всегда обновляет всех наблюдателей каждого субъекта, a DAGChangeManager обрабатывает направленные ациклические графы зависимостей между субъектами и их наблюдателями. Когда наблюдатель должен «присматривать» за несколькими субъектами, предпочтительнее использовать DAGChangeManager. В этом случае изменение сразу двух или более субъектов может привести к избыточным обновлениям. Объект DAGChangeManager гарантирует, что наблюдатель в любом случае получит только одно уведомление. Если обновление одного и того же наблюдателя допускается несколько раз подряд, то вполне достаточно объекта SimplechangeManager:

    ChangeManager - это пример паттерна посредник. В общем случае есть только один объект ChangeManager, известный всем участникам. Поэтому полезен будет также и паттерн одиночка. источник оригинал codelab.ru codelab.ru
  9. Комбинирование классов Subject и Observer.
    В библиотеках классов, которые написаны на языках, не поддерживающих множественного наследования (например, Java, Smalltalk), обычно не определяются отдельные классы Subject и Observer, а лишь интерфейсы. Поэтому эти роли можно легко совместить в одном классе. Это позволяет определить объект, выступающий в роли одновременно субъекта и наблюдателя, без множественного наследования. Так, в Java Subject и Object определены как интерфейсы Observable/Observer, в Smalltalk – оба в корневом классе Object и потому доступны вообще всем классам. codelab.ru codelab.ru источник оригинал

Результаты

Паттерн наблюдатель позволяет изменять субъекты и наблюдатели независимо друг от друга. Субъекты разрешается повторно использовать без участия наблюдателей, и наоборот. Это дает возможность добавлять новых наблюдателей без модификации субъекта или других наблюдателей. источник оригинал codelab.ru codelab.ru
Рассмотрим некоторые достоинства и недостатки Паттерн наблюдатель: codelab.ru источник codelab.ru оригинал
  1. Абстрактная связанность субъекта и наблюдателя.
    Субъект имеет информацию лишь о том, что у него есть ряд наблюдателей, каждый из которых подчиняется простому интерфейсу абстрактного класса Observer. Субъекту неизвестны конкретные классы наблюдателей. Таким образом, связи между субъектами и наблюдателями носят абстрактный характер и сведены к минимуму.
    Поскольку субъект и наблюдатель не являются тесно связанными, то они могут находиться на разных уровнях абстракции системы. Субъект более низкого уровня может уведомлять наблюдателей, находящихся на верхних уровнях, не нарушая иерархии системы. Если бы субъект и наблюдатель представляли собой единое целое, то получающийся объект либо пересекал бы границы уровней (нарушая принцип их формирования), либо должен был находиться на каком-то одном доступном уровне (компрометируя абстракцию уровня). источник оригинал codelab.ru codelab.ru
  2. Поддержка широковещательных коммуникаций.
    В отличие от обычного запроса для уведомления, посылаемого субъектом, не нужно задавать определенного получателя. Уведомление автоматически поступает всем подписавшимся на него объектам. Субъекту не нужна информация о количестве таких объектов, от него требуется всего лишь уведомить своих наблюдателей. Поэтому мы можем в любое время добавлять и удалять наблюдателей.
    Наблюдатель сам решает, обработать полученное уведомление или игнорировать его. codelab.ru codelab.ru оригинал источник
  3. Неожиданные обновления.
    Поскольку наблюдатели не располагают информацией друг о друге, им неизвестно и о том, во что обходится изменение субъекта. Безобидная, на первый взгляд, операция над субъектом может вызвать целый ряд обновлений наблюдателей и зависящих от них объектов. Более того, нечетко определенные или плохо поддерживаемые критерии зависимости могут стать причиной непредвиденных обновлений, отследить которые очень сложно.
    Эта проблема усугубляется еще и тем, что простой протокол обновления не содержит никаких сведений о том, что именно изменилось в субъекте. Без дополнительного протокола, помогающего выяснить характер изменений, наблюдатели будут вынуждены проделать сложную работу для косвенного получения такой информации. источник codelab.ru оригинал codelab.ru

Пример

Рассмотрим пример простого одного из возможных методов мониторинга пользователей системы на основе протокола Observer/Observable. Вначале определим класс Users, производный от Observabse: Users. источник оригинал codelab.ru codelab.ru

Объект Users сохраняет список имен пользователей, подключившихся к системе. Когда очередной пользователь подключается к системе либо, наоборот, отключается от нее, всем объектам Observer передается его имя. Метод notifyObservers рассылает сообщения только в том случае, если статус пользователя (точнее внутренний флаг объекта Observer-а) изменился, следовательно, необходимо перед этим вызывать унаследованный метод setChanged объекта Users, поскольку иначе notifyObservers не выполнит ничего существенного. оригинал источник codelab.ru codelab.ru
оригинал codelab.ru codelab.ru источник
Вот как может выглядеть реализация метода update в расширенном классе Observer, который предусматривает возможности отслеживания сведений о пользователях системы: Eye. оригинал codelab.ru источник codelab.ru
  источник codelab.ru codelab.ru оригинал
Каждый объект Eye («глаз») наблюдает за определенным объектом Users. Когда пользователь подключается к системе либо отключается от нее, объект Eye получает уведомление об этом событии, поскольку в конструкторе Eye посредством вызова метода addObserver соответствующего объекта Users выполняется процедура регистрации Eye как «заинтересованного» объекта. Метод update выполняет проверку корректности параметра, а затем изменяет состояние объекта Eye в соответствии с тем, подключился ли пользователь к системе или «вышел» из нее. codelab.ru codelab.ru источник оригинал
codelab.ru источник оригинал codelab.ru
Анализ происшедшего события, выполняемый с помощью имени пользователя, в даннном случае намеренно упрощен. Вместо простого имени методу update можно было бы передавать объект специального типа, более полно описывающий природу события и объект, к которому оно относится; такой подход, возможно, возволил бы расширять набор функций программы без необходимости внесения изменений в существующий код. источник оригинал codelab.ru codelab.ru


Известные применения паттерна Наблюдатель (Observer)

Первый и, возможно, самый известный пример паттерна наблюдатель появился в схеме модель/вид/контроллер (MVC) языка Smalltalk, которая представляет собой каркас для построения пользовательских интерфейсов в среде Smalltalk. Класс Model в MVC – это субъект, a View – базовый класс для наблюдателей. В языках Smalltalk, ET++ и библиотеке классов THINK предлагается общий механизм зависимостей, в котором интерфейсы субъекта и наблюдателя помещены в класс, являющийся общим родителем всех остальных системных классов. codelab.ru codelab.ru оригинал источник

  источник codelab.ru оригинал codelab.ru
В языке Java наблюдатели определены самым явным образом: в ядре языка имеется интерфейс Observer и класс Observable. Семантика и названия методов оповещения и подписки, конечно, отличиются от приведенных в этом описании, но задачи решаются в точности те же самые: Observer (Subject) обладает методами добавления удаления объектов Observable, а также setChanged, который вызывает notifyObservers (аналог Notify); интерфейс Observer в точности поддерживает тот же единственный метод Update, которому автоматически передается сам вызвавший это объект Observable (таким образом автоматически поддерживается схема наблюдения более чем за одним субъектом) и другой произвольный объект, инкапсулирующий список аргументов. codelab.ru оригинал codelab.ru источник
  codelab.ru codelab.ru оригинал источник
Среди других библиотек, в которых используется паттерн наблюдатель, стоит упомянуть Interviews, Andrew Toolkit и Unidraw. В Interviews как и в Java также явно определены классы Observer и Observable (для субъектов). В библиотеке Andrew они называются видом (view) и объектом данных (data object) соответственно. Unidraw делит объекты графического редактора на части View (для наблюдателей) и Subject. оригинал codelab.ru источник codelab.ru
codelab.ru оригинал источник codelab.ru

Родственные паттерны

Посредник: класс ChangeManager действует как посредник между субъектами и наблюдателями, инкапсулируя сложную семантику обновления. codelab.ru codelab.ru источник оригинал
Одиночка: класс ChangeManager может воспользоваться паттерном одиночка, чтобы гарантировать уникальность и глобальную доступность менеджера изменений. оригинал codelab.ru codelab.ru источник
codelab.ru источник оригинал codelab.ru


Реализации: java(2), javascript(1)   +добавить реализацию

1) Users.java, code #515[автор:this]
2) Eye.java, code #516[автор:this]


<< назад наверх
распечатать обсудить >>

 
каталог | задачи | паттерны | исходники | стат | форумы | карта сайта | контакты | ссылки 
© 2000-2017 CodeLAB Group
  Все права защищены
Страница сгенерирована за 0.049974 секунд
Количество запросов к БД: 14, gzip: 21.3kb/83.4kb(75%)