Видео курс по шаблонам проектирования. Observer - видео курсы ITVDN
ITVDN: курсы программирования
Видеокурсы по
программированию

    Выбери свою IT специальность

    Начать бесплатно

    Выбери свою IT специальность

    Начать бесплатно

      Видео курс по шаблонам проектирования

      ×

      Вы открыли доступ к тесту! Пройти тест

      Вы действительно хотите открыть доступ к тестированию по курсу Шаблоны проектирования на 40 дней?

      ВИДЕОУРОК №25. Observer

      Для просмотра полной версии видеокурса, онлайн тестирования и получения доступа к дополнительным учебным материалам купите курс Купить курс
      Для просмотра всех видеокурсов ITVDN, представленных в Каталоге, а также для получения доступа к учебным материалам и онлайн тестированию купите подписку Купить подписку
      В данном видео уроке рассматривается структура курса “Шаблоны проектирования” и производится краткий обзор книги “Приемы объектно-ориентированного проектирования. Паттерны проектирования” с обсуждением всех ее достоинств и недостатков. Также автор презентует книгу «Design Patterns via C#», которая в доходчивом, понятном и упрощенном виде объясняет назначение и применение паттернов в создании программного обеспечения.
      Читать дальше...
      Технически, паттерны (шаблоны) проектирования - это всего лишь абстрактные примеры правильного использования небольшого числа комбинаций простейших техник ООП. Видео урок представит простые примеры, показывающие правильные способы организации взаимодействий между классами или объектами.
      Читать дальше...
      Этот видео урок из "Курса шаблоны проектирования" посвящен UML (англ. Unified Modeling Language — унифицированный язык моделирования) — язык графического описания для объектного моделирования в области разработки программного обеспечения. UML является языком широкого профиля, это — открытый стандарт, использующий графические обозначения для создания абстрактной модели системы, называемой UML-моделью. Данный видеоурок познакомит вас c базовыми возможностями и подходами к проектированию с помощью языка UML.
      Читать дальше...
      Данный видео урок из курса "Шаблоны проектирования" познакомит Вас с понятием конечного автомата, вариантами его описания и логикой построения простейших конечных автоматов.
      Читать дальше...
      Видео урок раскроет понятие парадигмы программи́рования, которая является совокупностью идей и понятий, определяющих стиль написания компьютерных программ. Это способ концептуализации, определяющий организацию вычислений и структурирование работы, выполняемой компьютером. Этот видеоурок расскажет Вам об основных современных парадигмах объектно-ориентированного программирования, которые поддерживаются платформой .NET.
      Читать дальше...
      Данный видео урок дает базовые знания о регулярных грамматиках, и основных способах их применения в программировании.
      Читать дальше...
      Видео урок посвящен паттерну Abstract Factory, который предоставляет клиенту интерфейс (набор методов) для создания семейств взаимосвязанных или взаимозависимых объектов-продуктов, при этом скрывает от клиента информацию о конкретных классах этих объектов-продуктов.
      Читать дальше...
      Видеоурок посвящен паттерну проектирования Builder, который помогает организовать пошаговое построение сложного объекта-продукта так, что клиенту не требуется понимать последовательность шагов и внутреннее устройство строящегося объекта-продукта, при этом в результате одного и того же процесса конструирования могут получаться объекты-продукты с различным представлением (внутренним устройством).
      Читать дальше...
      Видео урок посвящен шаблону проектирования Factory Method, который предоставляет абстрактный интерфейс (набор методов) для создания объекта-продукта, но оставляет возможность, разработчикам классов, реализующих этот интерфейс самостоятельно принять решение о том, экземпляр какого конкретного класса-продукта создать. Паттерн Factory Method позволяет базовым абстрактным классам передать ответственность за создание объектов-продуктов своим производным классам.
      Читать дальше...
      Видеоурок расскажет о паттерне Prototype, который предоставляет возможность создания новых объектов-продуктов (клонов), используя технику клонирования (копирования) созданного ранее объекта-оригинала-продукта (прототипа). Паттерн Prototype – позволяет задать различные виды (классы-виды) объектов-продуктов (клонов), через настройку состояния каждого нового созданного клона. Классификация клонов-продуктов производится на основании различия их состояний.
      Читать дальше...
      Видео урок познакомит с шаблоном проектирования Singleton, который гарантирует, что у класса может быть только один экземпляр. В частном случае предоставляется возможность наличия, заранее определенного числа экземпляров.
      Читать дальше...
      Видео урок посвящен паттерну Adapter, он преобразует интерфейс (набор имен методов) одного класса в интерфейс (набор имен методов) другого класса, который ожидают клиенты. Адаптер обеспечивает совместную работу классов с несовместимыми интерфейсами, такая работа без Адаптера была бы невозможна.
      Читать дальше...
      Видео урок посвящен паттерну Bridge, который позволяет отделить абстракцию от элементов ее реализации так, чтобы и абстракцию, и реализацию можно было изменять независимо друг от друга.
      Читать дальше...
      Виде урок расскажет о шаблоне проектирования Composite, который составляет из объектов древовидные структуры для представления иерархий «часть – целое». Позволяет клиентам единообразно трактовать индивидуальные объекты (листья) и составные объекты (ветки).
      Читать дальше...
      Видео урок посвящен паттерну Decorator(декоратор), который динамически (в ходе выполнения программы) добавляет объекту новые возможности (состояние и/или поведение). Композиция, используемая при реализации паттерна Decorator, является гибкой альтернативой наследованию (порождению подклассов) с целью расширения функциональности.
      Читать дальше...
      Видео урок посвящен шаблону проектирования Facade, который предоставляет унифицированный интерфейс (набор имен методов) вместо интерфейса некоторой подсистемы (набора взаимосвязанных классов или объектов).
      Читать дальше...
      Видео урок посвящен шаблону проектированию Flyweight, который описывает правильное применение техники создания «разделяемых объектов», для получения возможности эффективного использования большого числа объектов.
      Читать дальше...
      Видео урок посвящен шаблону проектирования Proxy, который предоставляет объект-заместитель для контроля доступа к другому объекту.
      Читать дальше...
      Видео урок посвящен паттерну проектирования Chain of Responsibility, который позволяет избежать привязки объекта-отправителя запроса к объекту-получателю запроса, при этом давая шанс обработать этот запрос нескольким объектам. Паттерн Chain of Responsibility связывает в цепочку объекты-получатели запроса и передает запрос вдоль этой цепочки, пока один из объектов, составляющих эту цепочку не обработает передаваемый запрос.
      Читать дальше...
      Видео урок расскажет о шаблоне проектирования Command (Команда), который позволяет представить запрос в виде объекта, позволяя клиенту конфигурировать запрос (задавая параметры для его обработки), ставить запросы в очередь, протоколировать запросы, а также поддерживать отмену операций.
      Читать дальше...
      Видео урок посвящен шаблону проектирования Interpreter (интерпретатор), который позволяет сформировать объектно-ориентированное представление грамматики для заданного языка, а также описывает правила создания механизма интерпретации (толкования) предложений этого языка.
      Читать дальше...
      Видео урок посвящен шаблону проектирования Iterator (Итератор), который представляет удобный и безопасный способ доступа к элементам коллекции (составного объекта), при этом не раскрывая внутреннего представления этой коллекции.
      Читать дальше...
      Видео урок посвящен шаблону проектирования Mediator (посредник), который предоставляет объект-посредник, скрывающий способ взаимодействия множества других объектов-коллег. Объект-посредник обеспечивает слабую связанность системы, избавляя объектов-коллег от необходимости явно ссылаться друг на друга, позволяя тем самым независимо изменять взаимодействия между объектами-коллегами.
      Читать дальше...
      Видео урок посвящен поведенческому шаблоны проектирования Memento (хранитель), который не нарушая инкапсуляции, фиксирует и выносит за пределы объекта-хозяина его внутреннее состояние так, чтобы позднее это вынесенное состояние можно было восстановить в исходном объекте-хозяине.
      Читать дальше...
      Видео урок посвящен шаблону проектирования Observer (наблюдатель), который использует связь-отношения зависимости «один ко многим» (один издатель ко многим подписчикам). При изменении состояния одного объекта (издателя), все зависящие от него объекты (подписчики) оповещаются об этом и автоматически обновляются.
      Читать дальше...
      Видео урок расскажет о шаблоне проектирования State (Состояние), который позволяет объекту изменять свое поведение в зависимости от своего состояния. Поведение объекта изменяется на столько, что создается впечатление, что изменился класс объекта.
      Читать дальше...
      Видео урок посвящен шаблону проектирования Strategy (Стратегия), который определяет набор алгоритмов (часто схожих по роду деятельности), инкапсулирует каждый из имеющихся алгоритмов (в отдельный класс) и делает их подменяемыми. Паттерн Strategy позволяет подменять алгоритмы без участия клиентов, которые используют эти алгоритмы.
      Читать дальше...
      Видео урок посвящен паттерну проектирования Template Method, который формирует структуру алгоритма и позволяет в производных классах реализовать, заместить (перекрыть) или переопределить определенные шаги (участки) алгоритма, не изменяя структуру алгоритма в целом.
      Читать дальше...
      Видео урок посвящен шаблону проектирования Visitor (Посетитель), который позволяет единообразно обойти набор элементов с разнородными интерфейсами (т.е. набор объектов разных классов не приводя их к общему базовому типу), а также позволяет добавить новый метод (функцию) в класс объекта, при этом не изменяя сам класс этого объекта.
      Читать дальше...
      ПОКАЗАТЬ ВСЕ
      основные темы, рассматриваемые на уроке
      0:01:34
      Метафора
      0:08:38
      Структура паттерна на языке UML (Модель вытягивания)
      0:09:29
      Структура паттерна на языке UML (Модель проталкивание)
      0:09:49
      Структура паттерна на языке C# (Модель вытягивания)
      0:11:48
      Структура паттерна на языке C# (Модель проталкивание)
      0:13:56
      Отношение паттерна к общим архитектурным моментам
      0:18:42
      Комбинирование издателей и подписчиков
      0:22:19
      Назначение паттерна
      ПОКАЗАТЬ ВСЕ
      Рекомендуемая литература
      Рекомендуемой литературы к данному уроку не предусмотрено
      Титры видеоурока

      Паттерн Observer

      Название

      Наблюдатель

      Также известен как

      Dependents (Подчиненные), Publisher-Subscriber (Издатель-Подписчик)

      Классификация

      По цели: поведенческий

      По применимости: к объектам

      Частота использования

      Высокая                -   1 2 3 4 5

      Назначение

      Паттерн Observer – использует связь отношения зависимости «один ко многим» (один издатель ко многим подписчикам). При изменении состояния одного объекта (издателя), все зависящие от него объекты (подписчики) оповещаются об этом и автоматически обновляются.

      Введение

      Паттерн Observer описывает использование важной техники ООП - «Издатель-Подписчик», другими словами, паттерн Observer описывает правильные способы организации процесса подписки на определенные события.

      Кто такие издатель и подписчик в объективной реальности? Издателем может быть издательский центр - Microsoft Press который издает журнал «msdn magazine», а подписчиком может быть программист подписавшийся на данный журнал. 

      После того как подписчик подписался на журнал, подписчик ожидает пока издатель издаст журнал и оповестит об этом подписчика. Имеется два способа получения подписчиком журнала. Первый способ - «метод вытягивания»: После получения уведомления от издателя о том, что журнал выпущен, подписчик должен пойти к издателю и забрать (вытянуть) журнал самостоятельно.  Второй способ – «метод проталкивания»: Издатель не уведомляет подписчика о выпуске журнала, а самостоятельно или через почту доставляет журнал подписчику и, например, бросает (проталкивает) журнал в почтовый ящик.

      См. пример к главе: \019_Observer\ 004_MSDN Magazine

      Структура паттерна на языке UML

      Модель вытягивания (Pull model)

      См. Пример к главе: \019_Observer\001_Observer [project ObserverPull]

      Модель проталкивания (Push model)

      См. пример к главе: \019_Observer\001_Observer [project ObserverPush]

      Структура паттерна на языке C#

      Модель вытягивания (Pull model)

      См. пример к главе: \019_Observer\001_Observer [project ObserverPull]

      Модель проталкивания (Push model)

      См. пример к главе: \019_Observer\001_Observer [project ObserverPush]

      Участники

      • Subject - Субъект (издатель):

      Издатель содержит ссылки на своих подписчиков и предоставляет интерфейс (набор методов) для добавления и удаления подписчиков. На издателя может ссылаться любое число подписчиков.

      • Observer - Наблюдатель (подписчик):

      Подписчик предоставляет интерфейс (набор методов) для обновления своего состояния при изменении состояния издателя.

      • ConcreteSubject - Конкретный субъект (конкретный издатель):

      Конкретный издатель посылает уведомление своим подписчикам и передает им свое состояние.

      • ConcreteObserver  - Конкретный наблюдатель (конкретный подписчик):

      Реализует интерфейс обновления предоставляемый абстрактным классом Observer и поддерживает согласованность состояния с издателем.

      Отношения между участниками

      Отношения между классами

      • Абстрактный класс Subject связан связью отношения ассоциации с абстрактным классом Observer.
      • Конкретный класс ConcreteSubject связан связью отношения наследования с абстрактным классом Subject.
      • Конкретный класс ConcreteObserver связан связью отношения наследования с абстрактным классом Observer и связью отношения ассоциации с конкретным классом ConcreteSubject.

      Отношения между объектами

      • ConcreteSubject (издатель) уведомляет своих наблюдателей (подписчиков) о любом изменении, которое могло бы привести к рассогласованности состояний издателя и подписчика.
      • ConcreteObserver (подписчик) после получения от ConcreteSubject (издателя) уведомления об изменении состояния, может запросить у издателя дополнительную информацию для полного согласования своего состояния.
      • Метод Notify класса ConcreteSubject (издателя) может быть вызван не только издателем самостоятельно, также этот метод могут вызывать и подписчики, и посторонние клиенты.
      • Следует отметить, что часто для сохранения унификации способа уведомления подписчиков, приходится повторно уведомлять того подписчика, который передал издателю свое новое состояние. На диаграмме взаимодействий, показан пример, когда у подписчика изменилось состояние, и этот подписчик сообщил издателю об этом. После получения от подписчика уведомления о новом состоянии, издатель начинает обновлять состояние каждого из подписчиков, которые ему известны в том числе и состояние того подписчика, который сам только что передал издателю свое новое состояние.

      См. пример к главе: \019_Observer\005_Observer

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

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

      Мотивация

      Часто в ходе разработки программной системы, состоящей из множества классов, требуется организовать поддержку согласования (синхронизации) состояния взаимосвязанных объектов. При организации согласованности между объектами, рекомендуется обеспечить слабую связанность (Low Coupling) используемых классов, так как такой подход позволит увеличить гибкость и степень повторного использования элементов системы.

      Для построения программной системы чаще всего используется многослойная архитектура в которой «презентационные аспекты» (Presentation Layer) отделены от «аспектов данных» (Data Layer) и «бизнес сущностей» (Business Layer). Это значит, что классы элементов управления GUI и классы, относящиеся к бизнес логике будет располагаться в разных слоях программной системы и соответственно эти классы можно изменять независимо друг друга и работать с ними автономно.

      Например, на рисунке ниже показано, что объект – «электронная таблица» и объект – «диаграмма» ничего не знают друг о друге (не ссылаются друг на друга), поэтому их можно использовать по отдельности. 

      Когда пользователь работает с электронными таблицами, все изменения сразу же отражаются на диаграммах, и пользователю может показаться что все объекты взаимодействуют друг с другом напрямую. Но на самом деле взаимодействие между объектами «Подписчиками/Наблюдателями» происходит через объект «Издатель/Субъект». При таком подходе, все подписчики (электронная таблица и диаграммы) зависят от издателя (Субъекта). Соответственно, если изменяется состояние одного из подписчиков, этот подписчик уведомляет о своем изменении издателя, а издатель в свою очередь уведомляет всех остальных подписчиков, тем самым приводя согласованности состояния всех подписчиков. При этом нет ограничения на количество подписчиков и для работы с одними данными (a = 50%, b = 30%, c = 20%) может существовать любое число пользовательских интерфейсов (например, диаграмм-подписчиков).

      Паттерн Observer описывает способы организации отношений по принципу «издатель-подписчик». Ключевыми объектами в схеме паттерна Observer являются объект «издатель/субъект» и объект «подписчик/наблюдатель». У издателя может быть сколько угодно зависимых от него подписчиков. Все подписчики уведомляются об изменении состояния издателя и синхронизируют с издателем свое состояние. Издатель (субъект) отправляет уведомления не делая предположений об устройстве и внутренней структуре подписчиков.

      Применимость паттерна

      Паттерн Observer рекомендуется использовать, когда:

      • У абстракции (подсистемы) имеется два аспекта (например, презентационный аспект и бизнес составляющая), при этом один из аспектов зависит от другого. Выражение этих аспектов в разных объектах, позволяет изменять и повторно использовать эти объекты независимо друг от друга.
      • При изменении состояния одного объекта требуется изменить состояние других объектов, при этом заранее может быть не известно, сколько объектов нужно изменить.
      • Один объект должен оповещать другие объекты, при этом не делая никаких предположений об уведомляемых объектах, другими словами, требуется добиться слабой связанности между объектами.

      Результаты

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

      Паттерн Observer обладает следующими преимуществами:

      • Абстрактная связанность издателя и подписчика.

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

      В связи с тем, что издатель и его подписчики не являются сильно связанными, то они могут находиться в разных функциональных слоях системы (например, издатель в бизнес слое «Business Layer», а подписчики в слое представления «Presentation Layer»).           

      Издатель, располагающийся в более низком слое «Business Layer», может уведомлять подписчиков, располагающихся в более высоком слое «Presentation Layer», не нарушая правил построения многослойной системы. Если бы издатель и подписчик представляли собой нечто единое целое (объединенная логика издателя и подписчика в одном классе), то получившийся объект либо каким-то образом пересекал бы границы функционального слоя (нарушая принципы формирования слоев и их компонентов), либо должен был бы полностью находиться в каком-то одном слое, тем самым компрометируя абстракцию определенного слоя (Например, в объективной реальности, слой «кухня», можно скомпрометировать, разместив в этом слое объект-унитаз перенесенный из слоя «туалет»).

      • Поддержка широковещательных взаимодействий.

      Паттерн Observer позволяет организовать посылку запроса от издателя подписчикам, так что при этом не потребуется явно указывать каждого определенного получателя-подписчика. Запрос автоматически поступает всем подписчикам. Издатель пренебрегает информацией о количестве подписчиков, от издателя требуется только уведомить всех имеющихся подписчиков об изменении состояния издателя. Такой подход позволяет в любое время безопасно добавлять и удалять подписчиков (наблюдателей), при этом подписчики сами принимают решение о том, что делать с уведомлением, поступившим от издателя (обработать или игнорировать).

      Паттерн Observer обладает следующими недостатками:

      • Неожиданность обновлений.

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

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

      Реализация

      Полезные приемы реализации паттерна Observer:

      • Хранение ссылок в издателе на подписчиков.

      С помощью этого подхода издатель может отслеживать всех подписчиков, которым требуется посылать уведомления. Однако, при наличии (очень!) большого числа издателей и всего нескольких подписчиков, такой подход может оказаться накладным, так как в каждом издателе придется создавать коллекцию ссылок на подписчиков, что в совокупности может занять большой объем памяти. Чтобы сократить объем выделяемой памяти для хранения ссылок на подписчиков, можно воспользоваться ассоциативным массивом (например, хэш-таблицей - Hashtable) в котором будут храниться соответствия «издатель-подписчик». В таком случае издатели, которые не имеют подписчиков или имеют мало подписчиков не будут расходовать память на хранение ссылок, но при этом увеличится время поиска нужного подписчика в пуле подписчиков (хэш-таблице).

      • Подписка более чем на одного издателя.

      Иногда подписчик может подписаться на несколько издателей. Например, у электронной таблицы может существовать несколько источников данных. В таких случаях требуется расширить интерфейс обновления - метод Update(Subject publisher), вызываемый на подписчике, чтобы подписчик мог узнать какой издатель прислал уведомление. Издатель (Subject) может просто передать ссылку на себя в качестве аргумента метода subscriber.Update(this), тем самым сообщая подписчику (Observer) кто именно стал инициатором обновления состояния.

      • Кто может быть инициатором обновлений.

      Для поддержания согласованности состояний между издателем и подписчиками используется механизм уведомлений (вызов метода Notify принадлежащего издателю). Возникает вопрос, кто именно должен вызывать метод Notify для инициирования обновления? Имеется два варианта:

      1. Метод Notify вызывается непосредственно самим издателем (из методов класса Subject). Преимущество такого подхода заключается в том, что клиентам (Client) не надо помнить о необходимости вызова метода Notify класса Subject. Недостаток такого подхода в том, что при вызове определенных методов на издателе (Subject) эти методы могут вызвать метод Notify, тем самым внезапно инициировать волну обновлений подписчиков, что может стать причиной неэффективной работы программы.
      2. Метод Notify вызывается клиентом на экземпляре класса Subject. В роли клиента может выступать как объект класса (SomeClass) не входящего в структуру паттерна, так и объекты подписчики (Observer). Преимущество такого подхода заключается в том, что клиент может отложить инициирование обновления группы подписчиков до определенного времени, тем самым исключив ненужные промежуточные обновления. Недостаток такого подхода заключается в том, что у клиентов появляется дополнительная обязанность и клиент должен помнить о том, что в определенный момент нужно инициировать серию обновлений подписчиков. Это увеличит вероятность совершения ошибок клиентом, поскольку клиент должен понимать устройство подписчиков, вникать в технологические трудности работы каждого подписчика и в конце концов клиент может просто забыть вызвать метод Notify вовремя.
      • Наличие в подписчиках «висячих» ссылок на неиспользуемых издателей.

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

      • Гарантии непротиворечивости состояния издателя перед отправкой уведомления подписчикам.

      Перед вызовом метода Notify издатель должен находится в (правильном) состоянии. Состояние издателя, которое передается подписчикам не должно вызывать противоречий на стороне подписчиков. Другими словами, все значения полей издателя (состояние) которые передаются на сторону подписчиков, должны быть такими чтобы после передачи не вызвать на стороне подписчика недоразумений и не привести к физическим и логическим ошибкам.

      • Протоколы обновления: Модели вытягивания и проталкивания.

      В реализациях паттерна Observer, издателю часто требуется передать подписчикам дополнительную информацию о характере изменений. Такая информация передается в качестве аргумента метода Update и объем такой передаваемой информации может время от времени изменяться.

      Протокол обновления состояния подписчика имеет две модели: модель вытягивания (Pull model) и модель проталкивания (Push model)

      При использовании модели вытягивания, издатель не посылает подписчику ничего кроме минимального уведомления об изменении состояния, а подписчик уже самостоятельно запрашивает детали состояния у издателя (если это требуется подписчику). В модели вытягивания подчеркивается неинформированность издателя о своих подписчиках. Модель вытягивания может оказаться не эффективной, если подписчикам (Observer) потребуется информация о деталях произведенных изменений в состоянии издателя (Subject).

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

      • Явное указание представляющих интерес изменений.

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

      • Сокрытие сложного смысла обновления.

      Если отношения зависимости между издателями и подписчиками становятся сложными и запутанными (например, каждый из подписчиков подписан на несколько издателей), то может понадобиться объект (посредник) который уберет прямые связи отношений между объектами и выразит эти связи в форме алгоритма, а также возьмет под контроль все изменения состояний издателей и подписчиков. Такой объект можно назвать менеджером изменений (ChangeManager) и он будет служить для того чтобы подписчики могли корректно и своевременно синхронизироваться с состояниями издателей. Например, если выполнение некоторой операции повлечет за собой изменение состояний нескольких независимых издателей, то подписчики будут уведомлены об изменении состояния издателей только тогда, когда будут изменены состояния всех издателей, чтобы не уведомлять одного и того же подписчика несколько раз. Класс ChangeManager имеет три основных обязанности:

      1. Организация ссылочной целостности между издателем и подписчиками, а также предоставление интерфейса (набора методов) для поддержания ссылочной целостности в актуальном состоянии. Это освобождает издателей и подписчиков хранить ссылки друг на друга.
      2. Реализация (протокола) плана и правил обновления состояния.
      3. Обновление состояния всех подписчиков по требованию издателя.
      • На диаграмме ниже представлена диаграмма классов, описывающая реализацию паттерна Observer с использованием менеджера изменений ChangeManager

      См. пример к главе: \019_Observer\006_ObserverChangeManager

      • Комбинирование издателей и подписчиков.

      В тех языках, которые не поддерживают множественного наследования реализации (например, C#), обычно не создаются отдельные классы Subject и Observer, а их интерфейсы комбинируются в одном классе. Такой подход позволяет создать объекты, являющиеся одновременно и издателями, и подписчиками. В языке C# имеется специальный стереотип - delegate, выражающий идею технической комбинации издателя Subject и подписчика Observer в одном объекте. В основе делегатов лежит функциональная природа несмотря на имеющееся объектно-ориентированное выражение данного стереотипа. Функциональная основа делегата, позволила практически полностью убрать использование громоздкой объектно-ориентированной подписки на события, что соответственно привело к уменьшению числа связей в программах. Предлагается рассмотреть пример использования делегатов в схеме «издатель-подписчик»:

      delegate void SubjectObserver();
      
          class Program
          {
              // Update - логически относится к подписчику (Observer).
              static void Update()
              {
                  Console.WriteLine("Hello world!");
              }
      
              static void Main()
              {
                  SubjectObserver so = new SubjectObserver(Update);
      
                  // Аналог вызова Notify() - логически относится к издателю (Subject).
                  so.Invoke();
              }
          }
      

      См. пример к главе: \019_Observer\ 002_Observer Event [001_Observer]

      Приведенный пример показывает техническое, вульгарно-прямолинейное применение делегатов в схеме «издатель-подписчик», где делегат является «вещью в себе». Но, на подходе использования делегатов базируется полноценная событийная модель (event) платформы .Net. Событийная модель в .Net является логическим продолжением и более оптимальным выражением использования техники «издатель-подписчик», описываемой при помощи шаблона Observer.  Предлагается рассмотреть пример организации событийной модели с использованием конструкции языка C# - событием (event).  

      // Подписчик.
          delegate void Observer(string state);
      
      
          // Издатель.
          abstract class Subject
          {
              protected Observer observers = null;
      
              public event Observer Event
              {
                  add { observers += value; }
                  remove { observers -= value; }
              }
      
              public abstract string State { get; set; }
              public abstract void Notify();
          }
      
      
      
      
      
      
      
          // Конкретный издатель.
          class ConcreteSubject : Subject
          {
              public override string State { get; set; }
      
              public override void Notify()
              {
                  observers.Invoke(State);
              }
          }
      
          class Program
          {
              static void Main()
              {
                  // Издатель.
                  Subject subject = new ConcreteSubject();
      
                  // Подписчик, с сообщенным лямбда выражением.
                  Observer observer = new Observer(
      (observerState) => Console.WriteLine(observerState + " 1"));
      
                  // Подписка на уведомление о событии.
                  subject.Event += observer;
                  subject.Event +=
      (observerState) => Console.WriteLine(observerState + " 2");
      
                  subject.State = "State ...";
                  subject.Notify();
      
                  Console.WriteLine(new string('-', 11));
      
                  // Отписка от уведомлений.
                  subject.Event -= observer;
                  subject.Notify();
      
                  // Delay.
                  Console.ReadKey();
              }
          }
      

      См. пример к главе: \019_Observer\ 002_Observer Event [002_Observer]

      Нельзя смотреть «узко» на событийную модель и пытаться избавиться от объектно-ориентированного выражения издателя и подписчика в программных системах. Делегаты (delegate) и события (event) в .Net следует воспринимать как вспомогательный, сугубо технический механизм-связку для уменьшения числа явных связей отношений между объектами. По сути сами связи остаются, но уже не выражаются так явно и объектно-ориентированно. Эти связи выражены с использованием функционального подхода, функциональной природы делегата и соответственно на этих связях программисты не делают акцент при анализе системы. Связи в схеме «издатель-подписчик» стали чем-то само собой разумеющимся и неявным. 

      Пример кода

      Интерфейс подписчика определен в абстрактном классе Observer.

      abstract class Observer
          {
              public abstract void Update(Subject theChangedSubject);
          }
      

      При такой реализации поддерживается несколько издателей для одного подписчика. Передача издателя в качестве параметра метода Update позволяет подписчику определить, состояние какого из издателей изменилось.

      В абстрактном классе Subject определен интерфейс издателя.

      abstract class Subject
          {
              protected List observers = new List();
      
              public virtual void Attach(Observer observer)
              {
                  observers.Add(observer);
                  observer.Update(this);
              }
      
              public virtual void Detach(Observer observer)
              {
                  observers.Remove(observer);
              }
      
              public virtual void Notify()
              {
                  foreach (var o in observers)
                      o.Update(this);
              }
          }
      

      ClockTimer – это конкретный издатель, который следит за временем и оповещает подписчиков каждую секунду. Класс ClockTimer предоставляет интерфейс для получения отдельных компонентов времени: часы, минуты, секунды и т.д.

      class ClockTimer : Subject
          {
              System.Threading.Timer timer;
              private TimeSpan currentTime;
      
              static void TimerProc(object o)
              {
                  (o as ClockTimer).Tick();
              }
      
              public ClockTimer()
              {
                  timer = new System.Threading.Timer(TimerProc, this, 1000, 1000);
              }
      
      
      
      
      
              public void Tick()
              {
                  currentTime = DateTime.Now.TimeOfDay;
                  Notify();
              }
      
              public int GetHour()
              {
                  return currentTime.Hours;
              }
      
              public int GetMinute()
              {
                  return currentTime.Minutes;
              }
      
              public int GetSecond()
              {
                  return currentTime.Seconds;
              }
      
              public TimeSpan GetTime()
              {
                  return currentTime;
              }
          }
      

      Операция Tick вызывается через одинаковые интервалы внутренним таймером, тем самым обеспечивая правильный отсчет времени. При этом обновляется внутреннее состояние объекта ClockTimer и вызывается метод Notify для уведомления подписчиков об изменении времени.

      public void Tick()
              {
                  currentTime = DateTime.Now.TimeOfDay;
                  Notify();
              }
      

      Класс DigitalClock отображает время в цифровом формате.

      class DigitalClock : Observer
          {
              Label digitalClockLabel;
              Subject subject;
              TimeSpan time;
      
              public Control GetControl
              {
                  get { return digitalClockLabel; }
              }
      
              public DigitalClock(Control parent, Subject subject)
              {
                  digitalClockLabel = new Label { Parent = parent };
                  this.subject = subject;
                  subject.Attach(this);
              }
      
      
      
              public override void Update(Subject theChangedSubject)
              {
                  time = (theChangedSubject as ClockTimer).GetTime();
                  digitalClockLabel.BeginInvoke(new Action(Draw));
              }
      
              public void Draw()
              {
                  digitalClockLabel.Text = time.ToString("hh\\:mm\\:ss");
              }
          }
      

      Класс AnalogClock отображает время в аналоговом формате.

      class AnalogClock : Observer
          {
              class AnalogClockPanel : Panel
              {
                  public AnalogClockPanel()
                  {
                      SetStyle(ControlStyles.UserPaint |
                               ControlStyles.OptimizedDoubleBuffer |
                               ControlStyles.AllPaintingInWmPaint,
                               true);
                      DoubleBuffered = true;
                  }
              }
      
              Panel analogClockPanel;
              Subject subject;
              TimeSpan time;
              Point center = new Point(50, 50);
      
              public AnalogClock(Control parent, Subject subject)
              {
                  analogClockPanel = new AnalogClockPanel() { Parent = parent };
      
                  analogClockPanel.Size = new System.Drawing.Size(100, 100);
                  this.subject = subject;
                  subject.Attach(this);
              }
      
              public Control GetControl
              {
                  get { return analogClockPanel; }
              }
      
              public override void Update(Subject theChangedSubject)
              {
                  time = (theChangedSubject as ClockTimer).GetTime();
                  analogClockPanel.Invoke(new Action(Draw));
              }
      
      
      
      
      
      
      
              public void Draw()
              {
                  analogClockPanel.Refresh();
                  var g = analogClockPanel.CreateGraphics();
                  var rr = analogClockPanel.ClientRectangle;
                  rr.Width -= 1;
                  rr.Height -= 1;
                  g.DrawEllipse(new Pen(Color.Black, 1), rr);
      
                  for (int i = 0; i < 12; i++)
                  {
                      var rrr = (float)(Math.PI * 2f) / 60f * (float)(i * 5);
                      var from = GetDestinationPoint(rrr, 45);
                      var to = GetDestinationPoint(rrr, 50);
                      drawLine(g, from, to, new Pen(Color.Blue, 2));
                  }
      
                  drawW(g, (float)(Math.PI * 2f) / 12f * (float)(time.Hours % 12), 30,
      new Pen(Color.Blue, 5));
                  drawW(g, (float)(Math.PI * 2f) / 60f * (float)time.Minutes, 45,
      new Pen(Color.Green, 3));
                  drawW(g, (float)(Math.PI * 2f) / 60f * (float)time.Seconds, 50,
      new Pen(Color.Red, 2));
              }
      
              Point GetDestinationPoint(float radians, int length)
              {
                  int a = center.Y - (int)(Math.Cos(radians) * (float)length);
                  int b = center.X + (int)(Math.Sin(radians) * (float)length);
                  return new Point(b, a);
              }
      
              void drawLine(Graphics g, Point from, Point to, Pen p)
              {
                  g.DrawLine(p, from, to);
              }
      
              void drawW(Graphics g, float radians, int length, Pen p)
              {
      
                  drawLine(g, center, GetDestinationPoint(radians, length), p);
              }
          }
      

      Результат работы программы:

      См. пример к главе: \019_Observer\007_ObserverClocks

      Известные применения паттерна в .Net

      Паттерн Observer, выражен в языке C# в виде идеи использования функционально-ориентированного стереотипа - делегата (delegate) и языковой конструкции - события (event), которая в свою очередь строится на использовании делегата. Так же имеется выражение паттерна Observer в FCL (Framework Class Library) виде двух интерфейсов (interface) - IObservable<out T> и IObserver<in T>. Ниже с использованием диаграмм языка DSL представлена структура паттерна Observer.

      Пример кода, демонстрирует использование интерфейсов IObservable<out T> и IObserver<in T>.

      Реализация подписчика:

      class ConcreteObserver : IObserver<string>
          {
              string name;
              string observerState;
              IDisposable unsubscriber;
      
              public ConcreteObserver(string name, IObservable<string> subject)
              {
                  this.name = name;
                  unsubscriber = subject.Subscribe(this);
              }
      
              // Реализация интерфейса IObserver<T>
              public void OnCompleted()
              {
                  unsubscriber.Dispose();
              }
              public void OnError(Exception error)
              {
                  Console.ForegroundColor = ConsoleColor.Red;
                  Console.WriteLine("Observer {0}, Error: {1}", name, error.Message);
                  Console.ForegroundColor = ConsoleColor.Gray;
              }
      
              // Аналог Update(argument) - модель проталкивания.
              public void OnNext(string value)
              {
                  observerState = value;
                  Console.WriteLine("Observer {0}, State = {1}", name, observerState);
              }
          }
      

      Реализация издателя:

      class ConcreteSubject : IObservable<string>, IDisposable
          {
              public string State { get; set; }
      
              List<string> observers = new List<string>();
      
              public void Notify()
              {
                  foreach (IObserver<string> observer in observers)
                  {
                      if (this.State == null)
                          observer.OnError(new NullReferenceException());
                      else
                          observer.OnNext(this.State); // Модель проталкивания.
                  }
              }
      
      // Реализация интерфейса IObservable<T>
      // (UnSubscribe выполняется через IDisposable)
      
              /// 
              /// Подписать подписчика.
              /// 
              /// Конкретный подписчик
              /// Объект отписывающий подписанного подписчика
              public IDisposable Subscribe(IObserver<string> observer)
              {
                  if (!observers.Contains(observer))
                      observers.Add(observer);
      
                  return new Unsubscriber(observers, observer);
              }
      
              // Отписать всех подписчиков.
              public void Dispose()
              {
                  observers.Clear();
              }
      
      
              // Nested Class
              class Unsubscriber : IDisposable
              {
                  List<string> observers;
                  IObserver<string> observer;
      
                  public Unsubscriber(List<string> observers,
                                      IObserver<string> observer)
                  {
                      this.observers = observers;
                      this.observer = observer;
                  }
      
                  public void Dispose()
                  {
                      if (observers.Contains(observer))
                          observers.Remove(observer);
                      else
                          observer.OnError(new Exception("Данный подписчик не подписан"));
                  }
              }
          }
      

      Использование:

      class Program
          {
              static void Main()
              {
                  // Создание издателя.
                  ConcreteSubject subject = new ConcreteSubject();
      
                  // Создание подписчиков.
                  ConcreteObserver observer1 = new ConcreteObserver("1", subject);
                  ConcreteObserver observer2 = new ConcreteObserver("2", subject);
                  ConcreteObserver observer3 = new ConcreteObserver("3", subject);
                  ConcreteObserver observer4 = new ConcreteObserver("4", subject);
      
                  // Подписание подписчиков на издателя с получением объекта для отписки.
                  IDisposable unsubscriber1 = subject.Subscribe(observer1);
                  IDisposable unsubscriber2 = subject.Subscribe(observer2);
                  IDisposable unsubscriber3 = subject.Subscribe(observer3);
                  IDisposable unsubscriber4 = subject.Subscribe(observer4);
      
                  using (subject)
                  {
                      // Попытка предоставить подписчикам некорректное состояние.
                      subject.State = null;
                      subject.Notify();
                      Console.WriteLine(new string('-', 70) + "1");
      
                      // Отписка первого подписчика через
                      // ConcreteSubject.Unsubscriber.Dispose()
                      using (unsubscriber1)
                      {
                          // Попытка предоставить подписчикам корректное состояние.
                          subject.State = "State 1 ...";
                          subject.Notify();
                      }
      
                      Console.WriteLine(new string('-', 70) + "2");
      
                      // State 2 - получат только три подписчика
                      // которые остались подписанными.
                      subject.State = "State 2 ...";
                      subject.Notify();
      
                      Console.WriteLine(new string('-', 70) + "3");
      
                      // Отписка второго подписчика через ConcreteObserver.OnCompleted()
                      observer2.OnCompleted();
      
                      // State 3 - получат только 2 подписчика
                      // которые остались подписанными.
                      subject.State = "State 3 ...";
                      subject.Notify();
                  } // observers.Clear()
      
                  Console.WriteLine(new string('-', 70) + "4");
      
                  // Попытка отписать уже отписанного подписчика, обрабатывается в
                  // ConcreteSubject.Unsubscriber.Dispose()
                  observer4.OnCompleted();
      
                  // Delay.
                  Console.ReadKey();
              }
          }
      

      Результат работы программы:

      См. пример к главе: \019_Observer\003_IObserver

      Пакеты подписки с доступом ко всем курсам и сервисам

      Пакеты подписки с доступом ко всем курсам и сервисам

      Стартовый
      • Все видеокурсы на 3 месяца
      • Тестирование по 10 курсам
      • Проверка 5 домашних заданий
      • Консультация с тренером 30 мин
      49.99 $
      25.00 $
      Подписка
      Базовый
      • Все видеокурсы на 6 месяцев
      • Тестирование по 16 курсам
      • Проверка 10 домашних заданий
      • Консультация с тренером 60 мин
      89.99 $
      45.00 $
      Подписка
      Премиум
      • Все видеокурсы на 12 месяцев
      • Тестирование по 24 курсам
      • Проверка 20 домашних заданий
      • Консультация с тренером 120 мин
      169.99 $
      85.00 $
      Подписка
      комментарии и обсуждения
      Notification success
      Мы используем cookie-файлы, чтобы сделать взаимодействие с нашими веб-сайтами и услугами простым и значимым.