Рекомендации по шаблону разработки Observer
В .NET конструктивный шаблон наблюдателя реализован в виде набора интерфейсов. Интерфейс System.IObservable<T> представляет поставщик данных, который также отвечает за предоставление реализации IDisposable, позволяющей наблюдателям отменять подписку на уведомления. Интерфейс System.IObserver<T> представляет наблюдателя. В этом разделе содержатся рекомендации, которым должны следовать разработчики при реализации шаблона разработки наблюдателя с помощью этих интерфейсов.
Потоки
Как правило, поставщик реализует метод IObservable<T>.Subscribe путем добавления определенного наблюдателя в список подписчиков, представленный неким объектом коллекции, и он реализует метод IDisposable.Dispose путем удаления определенного наблюдателя из списка подписчиков. Наблюдатель может вызвать эти методы в любое время. Кроме того, поскольку в контракте поставщика и наблюдателя не указано, кто несет ответственность за отмену подписки после реализации метода обратного вызова IObserver<T>.OnCompleted, попытаться удалить тот же самый элемент из списка может как поставщик, так и наблюдатель. В связи с этим методы Subscribe и Dispose должны быть потокобезопасными. Как правило, в этом случае предполагается использование параллельной коллекции или блокировки. Реализации, которые не являются потокобезопасными, должны явным образом сообщать об этом.
Любые дополнительные гарантии должны быть указаны на уровне выше контракта поставщика и наблюдателя. Чтобы избежать недоразумений, связанных с контрактом наблюдателя, разработчики должны четко сообщать о введении дополнительных требований.
Обработка исключений
Из-за слабой связи между поставщиком данных и наблюдателем исключения в шаблоне разработки наблюдателя носят информативный характер. Это оказывает влияние на процесс обработки исключений поставщиками и наблюдателями в шаблоне разработки наблюдателя.
Поставщик— вызов метода OnError
Метод OnError предназначен для использования в качестве информационного сообщения для наблюдателей и очень похож на метод IObserver<T>.OnNext. Однако метод OnNext позволяет предоставлять наблюдателю текущие или обновленные данных, тогда как метод OnError указывает на то, что поставщик не может предоставлять допустимые денные.
При обработке исключений и вызове метода OnError поставщику следует придерживаться приведенных ниже рекомендаций.
Поставщик должен обрабатывать свои собственные исключения при наличии конкретных требований.
Поставщик не должен ожидать или требовать, чтобы наблюдатели обрабатывали исключения каким-либо определенным образом.
При обработке исключения, подвергающего риску его возможности по предоставлению обновлений, поставщику следует вызывать метод OnError. Информацию по таким исключениям можно передать наблюдателю. В других случаях уведомлять наблюдателей об исключении не нужно.
После того, как поставщик вызовет метод OnError или IObserver<T>.OnCompleted, дальнейшие уведомления поступать не должны, и поставщик может отменить подписку для своих наблюдателей. Однако наблюдатели также могут отменить свою подписку в любой момент — до или после получения уведомления OnError или IObserver<T>.OnCompleted. В шаблоне разработки наблюдателя не указывается, кто несет ответственность за отмену подписки. Таким образом, есть вероятность, что отменить подписку попытается и поставщик, и наблюдатель. Обычно наблюдатель, отменяющий подписку, удаляется из коллекции подписчиков. В однопоточном приложении перед удалением объекта реализация IDisposable.Dispose должна гарантировать, что ссылка на объект является действительной и объект входит в коллекцию подписчиков. В многопоточном приложении следует использовать объект потокобезопасной коллекций, такой как System.Collections.Concurrent.BlockingCollection<T>.
Наблюдатель — реализация метода OnError
Когда наблюдатель получает от поставщика уведомление об ошибке, он должен рассматривать исключение как информационное и не обязан выполнять никаких конкретных действий.
При ответе на вызов метода OnError от поставщика наблюдателю следует придерживаться приведенных ниже рекомендаций.
Наблюдатель не должен вызывать исключения из своих реализаций интерфейса, таких как OnNext или OnError. Однако если наблюдатель не создает исключения, следует ожидать, что исключения останутся необработанными.
Чтобы сохранить стек вызовов, наблюдатель, желающий создать объект Exception, который был передан в его метод OnError, должен упаковать исключение до его создания. Для этой цели можно использовать стандартный объект исключения.
Дополнительные рекомендации
Результатом попытки отмены регистрации в методе IObservable<T>.Subscribe может быть пустая ссылка. Поэтому рекомендуется воздержаться от таких действий.
Несмотря на наличие возможности прикрепить наблюдателя к нескольким поставщикам, рекомендуется присоединить экземпляр IObserver<T> только к одному экземпляру IObservable<T>.