Одного и тоже observer а можно использовать

Обновлено: 04.07.2024

Мартин Лютер узнал новость: Дева Мария имеет непорочное зачатие. булла Ineffabilis Deus. 8 декабря 1854 года Папа Пий IX Жан Кальвин узнал новость: Дева Мария имеет непорочное зачатие. булла Ineffabilis Deus. 8 декабря 1854 года Папа Пий IX Мартин Лютер узнал новость: Папа непогрешим. не всегда конечно, а только когда транслирует учение церкви ex cathedra. Первый Ватиканский собор 1869 год Жан Кальвин узнал новость: Папа непогрешим. не всегда конечно, а только когда транслирует учение церкви ex cathedra. Первый Ватиканский собор 1869 год

ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ СДЕЛАТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ

Mediator

На основе pub-sub строится работа паттерна Mediator, который позволяет наладить коммуникацию между различными компонентами системы. Mediator представляет собой глобальный объект в системе, о котором знают все компоненты системы, при этом компонент может выступать как слушателем события, так и издателем другого события, таким образом налаживая коммуникацию между объектами системы.

Если провести аналогию, то Mediator это городская АТС, в которую приходят входящие и исходящие вызовы от абонентов, а доходят они строго до нужного абонента. Но как мы знаем у телефонной сети есть недостаток — на новый год она может оказаться перегруженной огромным количеством звонков и перестать доставлять вызова абонентам. Тоже самое может произойти и с Mediator, когда он не справится с потоком событий.

Mediator особенно полезен в тех случаях, когда наблюдаются множественные однонаправленные или двунаправленные связи между различными компонентами системы. Особенно паттерн полезен, когда в приложении имеются вложенные друг в друга компоненты системы (например, дочерние композиционные элементы), чтобы не было необходимости пробрасывать callbacks используя модель всплытия события изнутри наружу. Достаточно предоставить Mediator внутреннему компоненту, который опубликует свое событие, а другие компоненты узнают об этом событии.

Mediator паттерн довольно успешно реализован в Backbone — сам глобальный объект Backbone можно использовать в качестве Mediator, либо унаследоваться от Backbone.Events.

Пишем реализацию Observer-а над KVO на objective-c

Почему в этом есть нужда?

Как вы, наверное, знаете — создание более менее внятных и серьезных приложений не может обойтись без грамотного проектирования. Одними из основных задач современного программирования — являются контроль над сложностью, требования создания гибких и расширяемых, изменяемых приложений. Из этого вытекают концепции ортогонального программирования, максимального уменьшения связности между классами, использования наиболее подходящих архитектурных решений (алсо грамотные подходы создания архитектуры проекта, подходы к проектированию классов). За многие человекочасы и человекодни мирового опыта всех разработчиков — были выработаны наиболее естественные и удачные подходы, названные паттернами проектирования… А подходы к проектированию классов — могут в некоторой степени изменяться, в зависимости от используемого языка программирования и требуемых свойств объекта. Описываемый сегодня мной паттерн является одним из моих самых любимых (и вообще достаточно значимый), а именно встречайте:… "Observer" (по-русски — Наблюдатель). Исходя из последних двух предложений — вытекает название этой статьи.

Наиболее полное и детальное описание паттерна Наблюдатель вы можете получить в известной книге «Банды четырех» — «Приемы объектно-ориентированного проектирования. Паттерны проектирования»
Еще есть неплохая шпаргалка по паттернам

Все паттерны делятся на 3 вида
— Поведенческие
— Порождающие
— Структурные

Observer является поведенческим паттерном.


Классическая реализация выглядит следующим образом, но как обычно, возможны некоторые отклонения от стандартной реализации

Что это за «Наблюдатель», имеющиеся технологии

Наблюдатель позволяет снизить количество зависимостей в проекте, уменьшить связность, увеличить независимость объектов друг от друга (уменьшить знание одного объекта о другом, принцип инкапсуляции), и предлагает подход к решению некоторой группы задач. Касательно моего текущего проекта — у меня возникла следующая проблема:

Имелся контроллер представления для создания нового заказа (NewOrderViewController) в иерархии Navigation Controller-a, и от него шли переходы к другим представлениям (для выбора тарифа, для выбора перевозчика, для выбора маршрута, выбора даты заказа и выбора дополнительных сервисов). Ранее я вызывал пересчет цены заказа на viewWillAppear в NewOrderViewController, но это было не лучшее решение, потому-что требовалось отослать сетевой запрос, и пользователь мог некоторое время видеть индикатор ожидания (например). И вообще было бы логичнее совершать перерасчет цены заказа после изменения одного из упомянутых ранее параметров заказа. Можно было использовать бы делегирование (либо хранить слабые ссылки на NewOrderViewController), и вызывать в соответствующих местах метод перерасчета цены. Но этот подход чреват усложнением и некоторыми неудобствами. Был выбран более подходящий способ — создать наблюдателя, который будет отслеживать изменения моделей, вызывать у класса PriceCalculator-a метод перерасчета, который в свою очередь сообщал NewOrderViewController о результатах расчета цены/ моменте начала расчета цены с использованием делегирования.

Теперь нужно поговорить о том, как сконструировать наблюдателя. Эта абстракция должна быть максимально проста в использовании, максимально естественна и логична.

Во-первых нам нужно либо самостоятельно реализовывать одну из технологий наблюдения, либо воспользоваться какой-либо уже имеющейся.
— (если вручную) Сконструировать такую технологию можно с помощью создания отдельного потока выполнения и ран-лупа (цикла) с детектированием изменений соответствующих объектов, за которыми мы планируем вести наблюдение
— (если использовать уже что-либо готовое) Есть только 2 решения в стандартных фреймворках под iOS, способных удовлетворить решению подобной задачи
а) NSNotificationCenter (использование механизма уведомлений)
б) KVO (Key-value observing) (наблюдение за изменениями свойств классов)

У подхода с NSNotification-ами есть существенный недостаток — для этого пришлось бы перегружать сеттеры требуемых свойств, и создавать NSNotification c помощью - postNotification:
, а в некоторых местах и явно указывать

Наиболее существенный плюс KVO — минимальное влияние на наблюдаемый класс, также возможности конфигурирования наблюдаемости (observing options), относительная простота.
Имеется и довольно существенный недостаток — серьезное потребление производительности (в случае повсеместного использования), но в моем случае я решил с этим примириться
Таким образом, выбор пал на KVO

Key-value Observing

Для использования KVO вы должны понимать так-же основные принципы Key-value coding (кодирования ключ-значение)
KVO предоставляет методы добавления и исключения наблюдателя

И основной метод для регистрации изменения над наблюдаемыми свойствами

Так-же плюсами являются возможность выбирать NSKeyValueObservingOptions
— NSKeyValueObservingOptionNew — получает в NSDictionary новое значение (вызывается, когда значение изменяется)
— NSKeyValueObservingOptionOld — получает в NSDictionary старое значение (перед изменением)
— NSKeyValueObservingOptionInitial — метод обработки так-же срабатывает сразу же после назначения наблюдателя
— NSKeyValueObservingOptionPrior — обработчик срабатывает дважды (и до изменений, и после) (не уверен)
Опции аддитивны, можно выбирать сразу несколько, используя побитовое или

Еще один плюс — возможность отслеживать свойство не только текущего объекта, а и вложенных (все-таки keyPath)

Текущая реализация

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

Изначально была мысль создать базовый класс, реализующий наблюдателя, но было решено, что это нереентабельно. Поэтому все наблюдатели банально унаследовал от NSObject-ов. Так как наблюдатель должен реализовать отношение один-ко-многим, то был придуман механизм подписчиков. Каждый класс, который нуждается в оповещении об каких-либо изменениях — подписывается на наблюдателя и реализует соответствующий метод из протокола.

Каждый подписчик должен поддерживать протокол (для AddressPathObserver это —

Моя реализация далека от идеала, но мир в целом несовершенен, но от этого он становится таким уж плохим как некоторым кажется))

Observer

Observer представляет собой не что иное, как связь один-ко-многим. В упрощенном виде этот паттерн состоит из объекта наблюдения (subject) и наблюдателей (observers).

Принципиальная схема взаимодействия выглядит так:

Subject — реализует методы: observe, detach, notify, get, set.

Observer — реализует метод update.

Также subject содержит ссылки на всех observers, которые его слушают, а observer, в свою очередь содержит ссылку, на subject, на который он подписан.

Таким образом в этом паттерне наблюдается прямая связь между объектами, т.е. subject знает о всех своих observers и вручную оповещает их о происходящих в себе изменениях, вызывая метод update у каждого observer. Связь устанавливается методом observe, разрывается методом detach.

Subject хранит внутри себя свое состояние и все действия с его состоянием необходимо совершать используя get/set методы, чтобы при изменениях состояния вызывать метод notify. Подобная схема реализована в EmberJs.


Observer можно представить довольно неплохой картинкой (заимствовано тут):

Примерную реализацию можно найти на сайте Addy Osmani.

Observer vs Pub-Sub

Observer и Pub-sub, наверное самые известные паттерны взаимодействия в мире разработки интерфейсов и JavaScript. Но несмотря на свою известность, некоторые разработчики считают эти паттерны одинаковыми, что и послужило подспорьем написать данную статью.


Существует довольно много шаблонов проектирования, например, фасад, фабрика, адаптер, мост и др. Считается, что каждый разработчик применяет паттерны проектирования, просто решая повседневные задачи, при этом возможно он даже и не знает об их существовании. Например, такие паттерны, как фасад, фабрика или адаптер, вполне можно применять даже не изучая их, т.к. они довольно элементарные и являются логическим заключением при решении каких-либо задач. На мой взгляд, лучший паттерн — это наличие мозга в голове и желания сделать качественный продукт, ведь паттерны сами по себе не являются конечным решением, а дают всего лишь общее решение для типичных задач.

Но в мире интерфейсов и асинхронного JavaScript не возможно прожить без знания паттернов observer и pub-sub, так как знание и понимание этих шаблонов, значительно облегчает жизнь разработчику.

Observer и Pub-sub относятся к поведенческим паттернам (паттернам взаимодействия), т.е. применяются, когда требуется организовать взаимодействие между различными объектами системы.

Pub-sub

Pub-sub паттерн является одной из вариаций паттерна Observer. Исходя из названия в паттерне выделяют два компонента Publisher (издатель) и Subscriber (подписчик). В отличие от Observer, связь между объектами осуществляется посредством канала связи Event Channel (шины событий).

Publisher кидает свои события в Event Channel, а Subscriber подписывается на нужное событие и слушает его на шине, что обеспечивает отсутствие прямой связи между подписчиком и издателем.


Схематично Pub-sub и отличие от Observer, можно представить так:

  1. отсутствие прямой связи между объектами
  2. объекты сигнализируют друг другу событиями, а не состояниями объекта
  3. возможность подписываться на различные события на одном объекте с различными обработчиками

Одной из наиболее известных реализаций паттерна pub-sub является Backbone, AmplifyJs и др. DOM, в некоторой степени тоже реализует модель pub-sub.

Урок 1. Основы RxJava. Observable и Observer.

Этот урок начнем с паттерна Наблюдатель и разберемся, как он используется в RxJava. Рассмотрим основные понятия: Observable и Observer, и какие типы событий они используют. Далее разберем один теоретический и один практический примеры.

Версии RxJava

На момент создания этого курса, вторая версия RxJava была в статусе Release Candidate. Поэтому начало курса описывает первую версию. Но, во-первых, вторая версия очень похожа на первую, и уроки актуальны для обоих версий. А, во-вторых, если вы новичок в теме Rx, то вам будет сложно сразу понять главное новшество второй версии.

Поэтому предлагаю вам спокойно читать уроки, написанные по первой версии, т.к. все эти знания будут актуальны и для второй. А начиная с 11 урока мы просто перейдем на вторую версию. К тому моменту вы уже без проблем сможете понять главные отличия между ними.

Теория

Прежде чем начать обсуждать механизмы RxJava, давайте вспомним паттерн Наблюдатель. В нем есть объект, который генерирует какие-то события, и есть объект или объекты, которые подписываются и получают эти события. Я думаю, что в работе вы постоянно используете этот паттерн. Самый простой пример - обработчик нажатия кнопки.

В Java даже есть инструменты для этого паттерна - класс Observable и интерфейс Observer. Реализация интерфейса Observer - это объект, который ожидает событие. Observable - это класс, который всем переданным ему Observer-объектам сообщит о том, что событие наступило.

Эти же названия используются и в RxJava. И смысл их остался тем же: Observable генерирует событие, а Observer получает его. Но было значительно расширено само понятие "событие". В RxJava события, которые Observable передает в Observer, можно рассматривать как поток данных. И события в этом потоке имеют три типа:
1) Next - очередная порция данных
2) Error - произошла ошибка
3) Completed - поток завершен и данных больше не будет.

В качестве примера давайте рассмотрим поиск авиарейса. Пусть у нас есть метод, который умеет бегать по сайтам авиакомпаний, искать там по заданным критериям необходимый рейс и все полученные рейсы возвращать нам. Мы задаем ему пункт назначения, пункт отправления, дату - и он начинает работу.

По мере того, как он один за другим обрабатывает сайты, он генерирует события Next, в которые передает результат. Т.е. каждый найденный рейс - новое событие Next.

Если в работе метода произошла какая то серьезная ошибка и продолжение работы невозможно, метод отправит событие Error.

Если метод успешно обработал все известные ему сайты и закончил работу, он отправит нам событие Completed.

Какие места занимают во всей этой схеме Observable и Observer? Метод поиска при вызове возвращает нам объект Observable. А мы создаем Observer, в котором пишем код для обработки полученных событий, и подписываемся на этот Observable. По мере работы метода, Observable будет генерировать события, которые мы будем получать в нашем созданном Observer:
- если пришло событие Next с очередным рейсом, то берем рейс и, например, добавляем его в адаптер списка. Таким образом рейсы один за другим будут появляться в списке по мере их нахождения.
- если пришло событие Completed, значит мы можем выключить ProgressBar и уведомить пользователя, что поиск завершен
- если пришло событие Error, то уведомляем пользователя, что поиск был прерван с ошибкой

В этом примере я описал все три типа событий, чтобы было понятно, зачем они нужны. Но, вовсе необязательно в каждом потоке данных вы встретите все эти типы. Например, Observable, который сообщает нам о нажатиях на кнопку. В этом случае мы будем получать только событие Next при каждом нажатии. Событие Completed нам не придет, потому что пользователь может сколько угодно раз нажимать эту кнопку и никакого последнего нажатия там не будет. Ну и ошибку тут мы вряд ли получим.

Надеюсь, после этого введения и примеров у вас появилось понимание роли объектов Observable и Observer. Самое сложное поначалу - это просто отличать их друг от друга )


Практика

Давайте рассмотрим простейший пример Observable.

Создание Observable выглядит так:

Observable<String> - это описание означает, что Observable будет предоставлять данные типа String, т.е. каждое событие Next, которое он будет генерировать, будет приходить с объектом типа String. Метод Observable.from создает для нас Observable, который возьмет данные из указанного String массива и передаст их получателям

Создаем получателя, т.е. Observer:

Observer<String> - получатель данных типа String. Напомню, что он от Observable ожидает получения событий трех типов Next, Error и Completed. И под каждый тип у Observer есть свой одноименный метод:
onNext(String s) - в этот метод будут приходить данные
onError(Throwable e) - будет вызван в случае какой-либо ошибки и на вход получит данные об ошибке
onCompleted() - уведомление о том, что все данные переданы

Оба объекта созданы, осталось подписать Observer на Observable методом subscribe:

Сразу после подписки Observable передаст в Observer все данные (в метод onNext) и сигнал о том, что передача завершена (метод onCompleted).

Этот простой пример призван показать взаимодействие между Observable и Observer. Мы использовали в нем Observable, который умеет передавать данные из предоставленного ему массива. Но это только один из видов Observable. Дальше мы научимся создавать различные Observable.

Что дальше

Когда я только начинал изучать RxJava, у меня после рассмотрения таких примеров возникали вопросы типа:
- В какой момент Observable начал генерировать события: после создания или после подписки на него Observer-а?
- Что будет если подписать несколько Observer-ов: каждый получит свои данные или те, кто подписался позже, не получит ничего?
- Как создать свой Observable, который будет отправлять результаты работы моего кода?
- Как сделать, чтобы работа в Observable выполнялась в одном потоке (в смысле Thread), а результаты приходили в другом?

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

Кроме того, мы рассмотрим, какие возможности работы с потоками данных предоставляет RxJava. Например, вы можете взять поток данных и выполнять над ним различные преобразования: фильтровать данные или конвертировать данные из одного типа в другой. Можете объединять данные из разных потоков данных последовательно, параллельно или попарно.

Также в RxJava присутствует отличный инструментарий для работы с потоками (речь уже не о потоках данных, а потоках, которые Thread). Вы можете указать один поток для генерации данных в Observable, другой поток для выполнения каких либо операций преобразования над этими данными и третий поток, в котором данные будут приходить в Observer.

В общем, тема очень интересная и полезная, и этот курс поможет вам в ней разобраться.

Что? Где? Когда?

Когда и где следует применять каждый паттерн — дело каждого, но прежде чем применять, следует понять их отличия и особенности. Например, Observer паттерн представляет собой прямую связь между объектами и сигнализирует наблюдателю об изменении своего состояния. На мой взгляд, данный паттерн очень хорошо подходит при разработке различных форм с множеством полей ввода, когда необходимо реагировать на изменения значений полей формы (binding).

Паттерн Mediator, как уже было отмечено, особенно полезен, когда необходимо наладить однонаправленную или двунаправленную связь между разрозненными компонентами системы, например, в сложных интерфейсах с множеством композиционных элементов, когда необходимо пробросить событие из вложенного view наверх, чтобы в другом view отобразить какую-либо информацию или выполнить действие, зависящее от этого события.

Читайте также: