Práticas recomendadas para o padrão de design do observador
No .NET, o padrão de design de observador é implementado como um conjunto de interfaces. A interface System.IObservable<T> representa o provedor de dados, que também é responsável por fornecer uma implementação IDisposable que permite que os observadores cancelem a assinatura de notificações. A interface System.IObserver<T> representa o observador. Este tópico descreve as práticas recomendadas que os desenvolvedores devem seguir ao implementar o padrão de design de observador usando essas interfaces.
Threading
Normalmente, um provedor implementa o método IObservable<T>.Subscribe adicionando um observador específico a uma lista de assinantes que é representada por algum objeto de coleção e implementa o método IDisposable.Dispose removendo um determinado observador da lista de assinantes. Um observador pode chamar esses métodos a qualquer momento. Além disso, como o contrato de provedor/observador não especifica quem é responsável por cancelar a assinatura após o método de retorno de chamada IObserver<T>.OnCompleted, o provedor e o observador podem tentar ambos remover o mesmo membro da lista. Devido a essa possibilidade, tanto o método Subscribe quanto o método Dispose devem ser thread-safe. Normalmente, isso envolve o uso de uma coleção simultânea ou um bloqueio. As implementações que não são thread-safe devem documentar explicitamente esse fato.
Quaisquer garantias adicionais devem ser especificadas em uma camada no início do contrato de provedor/observador. Os implementadores devem chamar claramente ao imporem requisitos adicionais para evitar confusão do usuário sobre o contrato do observador.
Tratando exceções
Devido ao fraco acoplamento entre um provedor de dados e um observador, as exceções no padrão de design do observador devem ser informativas. Isso afeta como os provedores e observadores manipulam exceções no padrão de design do observador.
O Provedor - Chamando o Método OnError
O método OnError serve como uma mensagem informativa para observadores, da mesma forma que o método IObserver<T>.OnNext. No entanto, o método OnNext foi projetado para fornecer a um observador dados atuais ou atualizados, enquanto o método OnError foi projetado para indicar que o provedor não é capaz de fornecer dados válidos.
O provedor deve seguir essas práticas recomendadas ao manipular exceções e chamar o método OnError:
O provedor deverá manipular suas próprias exceções se houver algum requisito específico.
O provedor não deve esperar ou exigir que os observadores manipulem exceções de alguma maneira específica.
O provedor deve chamar o método OnError ao manipular uma exceção que comprometa sua capacidade de fornecer atualizações. Informações sobre essas exceções podem ser passadas para o observador. Em outros casos, não há necessidade de notificar os observadores com relação a uma exceção.
Uma vez que o provedor chame o método OnError ou IObserver<T>.OnCompleted, não deverá haver nenhuma notificação adicional e o provedor poderá cancelar a assinatura de seus observadores. No entanto, os observadores podem também cancelar eles próprios sua assinatura a qualquer momento, inclusive antes e depois de receberem uma notificação OnError ou IObserver<T>.OnCompleted. O padrão de design do observador não determina se o provedor ou o observador é responsável pelo cancelamento da assinatura; portanto, é possível que ambos tentem cancelar a assinatura. Normalmente, quando os observadores cancelam a assinatura, eles são removidos de uma coleção de assinantes. Em um aplicativo de thread único, a implementação IDisposable.Dispose deve garantir que uma referência de objeto seja válida e que o objeto seja um membro da coleção de assinantes antes de tentar removê-lo. Em um aplicativo com multithread, deve-se usar um objeto de coleção thread-safe, tal como um objeto System.Collections.Concurrent.BlockingCollection<T>.
O Observador — Implementando o Método OnError
Quando um observador recebe uma notificação de erro de um provedor, o observador deve tratar a exceção como informativa e não deve ser necessária qualquer ação específica.
O observador deve seguir essas práticas recomendadas ao responder a uma chamada de método OnError de um provedor:
O observador não deve lançar exceções de suas implementações de interface, tais como OnNext ou OnError. No entanto, se o observador lançar exceções, ele deverá esperar que essas exceções fiquem sem tratamento.
Para preservar a pilha de chamadas, um observador que deseja gerar um objeto Exception, que foi passado para o seu método OnError, deveria encapsular a exceção antes de lançá-la. Um objeto de exceção padrão deve ser usado para essa finalidade.
Práticas recomendadas adicionais
A tentativa de cancelar registro no método IObservable<T>.Subscribe pode resultar em uma referência nula. Portanto, é recomendável que você evite essa prática.
Embora seja possível anexar um observador para vários provedores, o padrão recomendado é anexar uma IObserver<T> instância a uma única instância IObservable<T>.