Prévia do material em texto
.NET – O padrão de projeto
Observer
PorJosé Carlos Macoratti em 06/06/2013
3091VIEWS
COMPARTILHE!
O padrão de projeto (Design Pattern) Observer é um padrão comportamental
que representa uma relação de 1-N (de um para muitos) entre objetos. Assim,
quando um objeto altera o seu estado, os objetos dependentes serão
notificados/informados/avisados e atualizados de forma automática. O padrão
possibilita que objetos sejam avisados da mudança de estado de outros
eventos ocorrendo em outro objeto.
Podemos dizer de forma simples que o objetivo principal do padrão Observer é
manter a consistência entre objetos relacionados sem criar um código
fortemente acoplado.
As expressões “acoplamento fraco” ou “acoplamento forte” são comuns em
quase todas as discussões sobre projeto de software. O acoplamento entre
classes ou subsistemas é uma medida da interconexão entre essas classes ou
subsistemas. O acoplamento forte significa que as classes relacionadas
precisam conhecer detalhes internos umas das outras, as alterações se
propagam pelo sistema, e o sistema é potencialmente mais difícil de entender e
manter.
Acoplamento é o nível de dependência/conhecimento que pode existir entre as
classes;
•
Uma classe com acoplamento fraco não é dependente de muitas classes para
fazer o que ele tem que fazer;
•
Uma classe com acoplamento forte depende de muitas outras classes para
fazer o seu serviço;
•
Uma classe com acoplamento forte é mais difícil de manter, de entender e de
ser reusada;
•
Em uma definição mais formal, um padrão Observer permite:
“Definir uma dependência um-para-muitos entre objetos para que
quando um objeto mudar de estado, todos os seus dependentes
sejam notificados e atualizados automaticamente.” [GoF]
Nota: Padrões de projeto comportamentais são padrões que tratam das
interações e divisões de responsabilidades entre as classes ou objetos.
O diagrama de classes para o padrão Observer é mostrado na figura abaixo:
sexta-feira, 4 de maio de 2018 03:01
Página 1 de Padrao Observer
Classes/Objetos participantes do padrão:
Sujeito (subject): Conhece os seus observadores. Um número qualquer de
observadores é permitido. Provê uma interface para adicionar/remover
observadores;
1.
Observador (observer): Define uma interface de atualização para os objetos
que devem ser notificados sobre as mudanças no sujeito;
2.
Sujeito concreto (ConcreteSubject): Armazena o estado de interesse para os
objetos de ConcreteObserver; envia uma notificação aos observadores
concretos quando seu estado mudar;
3.
Observador concreto (ConcreteObserver): Implementa a interface definida
pelo Observador para tratar as notificações.
4.
O mesmo diagrama de classes traduzido:
Vantagens em usar o padrão
Página 2 de Padrao Observer
Vantagens em usar o padrão
Observer
Permite um acoplamento mínimo entre o Sujeito(Subjet) e o
Observador(Observer)
•
Pode reutilizar Sujeitos sem reutilizar os seus observadores e vice-versa;•
Os Observadores podem ser adicionados sem modificar o Sujeito;•
Todo sujeito conhece a sua lista de Observadores;•
O Sujeito não precisa conhecer a classe concreta de um observador, apenas
que cada observador implementa a interface update();
•
O Sujeito e o Observador podem pertencer a diferentes camadas de abstração.•
Quando usar o padrão Observer?
Quando uma modificação do estado de um objeto implica modificações em
outros objetos;
•
Quando um objeto deve ser capaz de notificar outros objetos, mas sem
pressupostos sobre os objetos a serem notificados;
•
Quando uma abstração possuir dois aspectos e um depende do outro;•
Quando não desejamos um forte acoplamento com os objetos que necessitam
conhecer estas modificações;
•
Questões de Implementação do
padrão Observer
Como é que o sujeito vai manter o controle de seus observadores?
R: Usando um Array, uma lista ligada etc.
E o que ocorre se um observador quer observar mais de um sujeito?
R: Ele têm que dizer ao observador quem ele é através da interface de
atualização.
E quem aciona a atualização?
R: O sujeito, sempre que ele muda de estado; os observadores depois de
causar uma ou mais mudanças de estado; algum outro objeto.
Página 3 de Padrao Observer
Obs: Verifique se o Sujeito atualiza o seu estado antes de enviar notificações.
Quanta informação sobre a mudança deve o Sujeito enviar para os
observadores?
R: No modelo Push – bastante, ou no modelo Pull – muito pouca.
Um observador pode ser um Sujeito?
R: Sim, pode.
Implementando o padrão de projeto
Observer
Este exemplo foi baseado no livro “Use a Cabeça (head first) Padrões de
Projeto”, FREEMAN (2007:81-96).
Para implementar o padrão Observer eu vou usar o seguinte cenário:
Imagine que temos Assinantes de um serviço de uma Editora;•
A editora deverá notificar todos os seus assinantes quando houver uma nova
edição;
•
A nova edição será representada por variável booleana _novaEdicao que
inicialmente terá o seu valor definido como false;
•
O método alterarEdicao() promove a alteração do estado indicando true
quando houver uma nova edição;
•
O método getEdicao() obtém o valor atual de _novaEdicao indicando se há
uma nova edição ou não.
•
Nesse cenário, teremos as seguintes classes:
Sujeito – Interface que define os métodos para Registrar/Remover/Notificar um
observador;
1.
Observador – Interface que define o método Atualizar() que informa o
observador quando houver uma alteração do estado do Sujeito;
2.
EditoraConcreta – Classe concreta que implementa interface Sujeito;3.
AssinanteConcreto – Classe concreta que implementa a interface Observador.4.
O Diagrama de classes gerado é visto abaixo:
Página 4 de Padrao Observer
Implementando o padrão Observer
Vamos agora criar as classes e verificar o seu funcionamento.
Eu vou usar o Visual Studio 2012 Express Edition e criar uma aplicação
Console chamada PadraoProjetoObserver.
Obs: Estou disponibilizando também o código para VB .NET criado no Visual
Studio 2012 Express Edition
No menu Project, clique em Add Class e informe o nome Sujeito.cs/Sujeito.vb e
a seguir digite o código da classe Sujeito conforme abaixo:
C#
1 namespace PadraoProjetoObserver
2 {
Página 5 de Padrao Observer
2 {
3 interface Sujeito
4 {
5 void RegistrarObservador(Observador o);
6 void RemoverObservador(Observador o);
7 void NotificarObservadores();
8 }
9 }
A interface Sujeito de declara os métodos para registrar/remover e notificar
um observador
C#
1 namespace PadraoProjetoObserver
2 {
3 interface Observador
4 {
5 void Atualizar(Sujeito sujeito);
6 }
7 }
A interface Observador declara o método Atualizar
C#
1 using System;
2 using System.Collections.Generic;
3
4 namespace PadraoProjetoObserver
5 {
6 class EditoraConcreta : Sujeito
7 {
8 private List<Observador> observadores = new List<Observador>();
9 private bool _novaEdicao = false;
10
Página 6 de Padrao Observer
11 public void RegistrarObservador(Observador o)
12 {
13 observadores.Add(o);
14 }
15
16 public void RemoverObservador(Observador o)
17 {
18 observadores.Remove(o);
19 }
20
21 public void NotificarObservadores()
22 {
23 foreach (Observador o in observadores)
24 {
25 o.Atualizar(this);
26 }
27 }
28
29 public void alterarEdicao()
30 {
31 if (_novaEdicao)
32 _novaEdicao = false;
33 else
34_novaEdicao = true;
35 NotificarObservadores();
36 }
37
38 public Boolean getEdicao()
39 {
40 return _novaEdicao;
41 }
42 }
43 }
Note que na classe concreta EditoraConcreta estamos chamando o
método NotificarObservadores() após ocorrer uma mudança de estado no
objeto EditoraConcreta;
Na implementação do método notificarObservadores, percorremos os
observadores informando cada instância dele mesmo e informando que o
Página 7 de Padrao Observer
observadores informando cada instância dele mesmo e informando que o
objeto que estava sendo observado mudou seu estado.
C#
1 using System;
2
3 namespace PadraoProjetoObserver
4 {
5 class AssinanteConcreto : Observador
6 {
7 private EditoraConcreta objetoObservado;
8
9 public AssinanteConcreto(EditoraConcreta o)
10 {
11 objetoObservado = o;
12 objetoObservado.RegistrarObservador(this);
13 }
14
15 public void Atualizar(Sujeito sujeito)
16 {
17 if (sujeito == objetoObservado)
18 {
19 Console.WriteLine("[Aviso] - A editora alterou o seu
estado para : " + objetoObservado.getEdicao());
20 }
21 }
22 }
23 }
No construtor da classe AssinanteConcreto, criamos o observador e já
definimos como parâmetro o objeto observado, logo a seguir podemos chamar
o método adicionarObservador que passa por referência a sua própria
instância.
Quando o método atualizar é chamado, nós precisamos verificar se a editora
que alterou o estado é a mesma que estamos observando.
C#
Página 8 de Padrao Observer
C#
1 using System;
2
3 namespace PadraoProjetoObserver
4 {
5 class Program
6 {
7 static void Main(string[] args)
8 {
9 EditoraConcreta editora = new EditoraConcreta();
10 // Editora ja inicia com valor padrão igual a false
11 AssinanteConcreto assinante1 = new
AssinanteConcreto(editora);
12 AssinanteConcreto assinante2 = new
AssinanteConcreto(editora);
13 AssinanteConcreto assinante3 = new
AssinanteConcreto(editora);
14 AssinanteConcreto assinante4 = new
AssinanteConcreto(editora);
15 AssinanteConcreto assinante5 = new
AssinanteConcreto(editora);
16
17 // Já passando a editora como parametro
18 editora.alterarEdicao();
19 // Nesse momento é chamado o método atualizar
20 // das instâncias assinante1 e assinante2, resultadao:
21 // [Aviso] A Editora mudou seu estado para: true (5 x)
22 editora.alterarEdicao();
23 //[Aviso] A Editora mudou seu estado para: false (5 x)
24 // Obs: temos 5 saídas porque temos 5 assinantes
25 Console.ReadKey();
26 }
27 }
28 }
Poderíamos dar nomes aos assinantes usando uma string em
AssinanteConcreto e passar seu nome para o construtor como segundo
parâmetro.
Página 9 de Padrao Observer
O resultado acima mostra a notificação emitida a todos os assinantes
registrados após a mudança de estado.
Pegue a solução completa aqui:
PadraoProjetoObserver.zip
PadraoProjetoObserverVB.zip
De <https://imasters.com.br/desenvolvimento/visual-basic/net-o-padrao-de-projeto-observer/?trace=1519021197
&source=single>
Página 10 de Padrao Observer
Padrão de design do observador
30/03/2017•
20 minutos para ler•
O padrão de design do observador permite a um assinante se registrar em um
provedor e receber notificações dele.Ele é adequado para qualquer cenário que
requer a notificação por push. O padrão define um provedor (também conhecido
como assunto ou observável) e zero, um ou mais observadores. Observadores
registram-se com o provedor e, sempre que uma condição, evento ou alteração de
estado predefinido ocorrer, o provedor notificará automaticamente todos os
observadores chamando um dos seus métodos. Nessa chamada de método, o
provedor também pode fornecer informações sobre o estado atual para
observadores. No .NET Framework, o padrão de design de observador é aplicado
ao implementar as interfaces
genéricas System.IObservable<T> e System.IObserver<T>. O parâmetro de tipo
genérico representa o tipo que fornece informações de notificação.
Aplicando o padrão
O padrão de design do observador é adequado para notificações por push
distribuídas, pois oferece suporte para uma separação clara entre dois componentes
diferentes ou camadas de aplicativo, como uma camada de fonte de dados (lógica
de negócios) uma camada de interface do usuário (exibição). O padrão pode ser
implementado sempre que um provedor usa retornos de chamada para fornecer
informações atuais a seus clientes.
Um provedor ou o assunto, que é o objeto que envia notificações para
observadores. Um provedor é uma classe ou estrutura que implementa a
interface IObservable<T>. O provedor deve implementar um único
método, IObservable<T>.Subscribe, que é chamado pelos observadores que
desejam receber notificações do provedor.
•
Um observador, que é um objeto que recebe notificações de um provedor. Um
observador é uma classe ou estrutura que implementa a
interface IObserver<T>. O observador deve implementar três métodos, que são
chamados pelo provedor:
•
IObserver<T>.OnNext, que fornece ao observador informações novas ou
atuais.
•
IObserver<T>.OnError, que informa o observador que ocorreu um erro.•
IObserver<T>.OnCompleted, que indica que o provedor terminou de enviar
notificações.
•
Um mecanismo que permite que o provedor mantenha controle dos
observadores. Normalmente, o provedor usa um objeto de contêiner, como um
objeto System.Collections.Generic.List<T>, para manter as referências às
implementações de IObserver<T> que assinaram notificações. Usar um
contêiner de armazenamento para essa finalidade permite que o provedor lidar
com um número ilimitado de observadores. A ordem na qual os observadores
recebem notificações não está definida. O provedor está livre para usar
qualquer método para determinar a ordem.
•
Uma implementação de IDisposable que permite que o provedor remova os
observadores quando a notificação for concluída. Observadores recebem uma
referência para a implementação de IDisposable do método Subscribe,
•
A implementação do padrão exige que você forneça o seguinte:
terça-feira, 24 de abril de 2018 10:03
Página 11 de Padrao Observer
referência para a implementação de IDisposable do método Subscribe,
portanto, eles também podem chamar o método IDisposable.Dispose para
cancelar a assinatura antes que o provedor tenha terminado de enviar
notificações.
Um objeto que contém os dados que o provedor envia para seus
observadores. O tipo desse objeto corresponde ao parâmetro de tipo genérico
das interfaces IObservable<T> e IObserver<T>. Embora esse objeto possa ser
o mesmo que a implementação de IObservable<T>, geralmente ele é um tipo
separado.
•
Observação
Além de implementar o padrão de design do observador, pode ser interessante
explorar bibliotecas que são criadas usando as
interfaces IObservable<T> e IObserver<T>. Por exemplo, Extensões Reativas
para .NET (Rx)consistem em um conjunto de métodos de extensão e operadores de
sequência padrão LINQ para oferecer suporte à programação assíncrona.
Implementando o padrão
O exemplo a seguir usa o padrão de design do observador para implementar um
sistema de informações de coleta de bagagem de aeroporto. Uma
classe BaggageInfo fornece informações sobre voos que chegam e as esteiras
onde as bagagens de cada voo estão disponíveis pararetirada. Isso é mostrado no
exemplo a seguir.
C#Copiar
using System;
using System.Collections.Generic;
public class BaggageInfo
{
private int flightNo;
private string origin;
private int location;
internal BaggageInfo(int flight, string from, int carousel)
{
this.flightNo = flight;
this.origin = from;
this.location = carousel;
}
public int FlightNumber {
get { return this.flightNo; }
}
public string From {
get { return this.origin; }
}
public int Carousel {
get { return this.location; }
}
}
observers - Uma coleção de clientes que receberão informações atualizadas.•
flights - Uma coleção de voos e suas respectivas esteiras.•
Uma classe BaggageHandler é responsável por receber informações sobre os voos
que chegam e as esteiras de coleta de bagagem. Internamente, ela mantém duas
coleções:
Ambas as coleções são representadas por objetos List<T> genéricos que são
instanciados no construtor de classe BaggageHandler. O código-fonte para a
classe BaggageHandler é mostrado no exemplo a seguir.
Página 12 de Padrao Observer
classe BaggageHandler é mostrado no exemplo a seguir.
C#Copiar
public class BaggageHandler : IObservable<BaggageInfo>
{
private List<IObserver<BaggageInfo>> observers;
private List<BaggageInfo> flights;
public BaggageHandler()
{
observers = new List<IObserver<BaggageInfo>>();
flights = new List<BaggageInfo>();
}
public IDisposable Subscribe(IObserver<BaggageInfo> observer)
{
// Check whether observer is already registered. If not, add it
if (! observers.Contains(observer)) {
observers.Add(observer);
// Provide observer with existing data.
foreach (var item in flights)
observer.OnNext(item);
}
return new Unsubscriber<BaggageInfo>(observers, observer);
}
// Called to indicate all baggage is now unloaded.
public void BaggageStatus(int flightNo)
{
BaggageStatus(flightNo, String.Empty, 0);
}
public void BaggageStatus(int flightNo, string from, int carousel)
{
var info = new BaggageInfo(flightNo, from, carousel);
// Carousel is assigned, so add new info object to list.
if (carousel > 0 && ! flights.Contains(info)) {
flights.Add(info);
foreach (var observer in observers)
observer.OnNext(info);
}
else if (carousel == 0) {
// Baggage claim for flight is done
var flightsToRemove = new List<BaggageInfo>();
foreach (var flight in flights) {
if (info.FlightNumber == flight.FlightNumber) {
flightsToRemove.Add(flight);
foreach (var observer in observers)
observer.OnNext(info);
}
}
foreach (var flightToRemove in flightsToRemove)
flights.Remove(flightToRemove);
flightsToRemove.Clear();
}
}
public void LastBaggageClaimed()
{
foreach (var observer in observers)
observer.OnCompleted();
Página 13 de Padrao Observer
observer.OnCompleted();
observers.Clear();
}
}
Clientes que desejem receber informações atualizadas devem chamar o
método BaggageHandler.Subscribe. Se o cliente não tiver assinado as
notificações anteriormente, uma referência para a implementação IObserver<T> do
cliente será adicionada à coleção observers.
O método BaggageHandler.BaggageStatus sobrecarregado pode ser chamado
para indicar que bagagem de um voo está sendo descarregada ou que não está
mais sendo descarregada. No primeiro caso, o método recebe um número de voo, o
aeroporto que originou o voo e a esteira onde bagagem está sendo
descarregada. No segundo caso, o método recebe apenas um número de voo. Para
a bagagem que está sendo descarregada, o método verifica se as informações
de BaggageInfo passadas para o método existem na coleção flights. Se não
existirem, o método adicionará as informações e chamará o método OnNext de cada
observador. Para voos cuja bagagem não está mais sendo descarregada, o método
verificará se as informações sobre esse voo estão armazenadas na
coleção flights.Se estiverem, o método chamará o método OnNext de cada
observador e removerá o objeto BaggageInfo da coleção flights.
Quando o último voo do dia tiver aterrizado e sua bagagem tiver sido processada, o
método BaggageHandler.LastBaggageClaimed será chamado. Esse método
chama o método OnCompleted de cada observador para indicar que todas as
notificações foram finalizadas e, em seguida, limpa a coleção observers.
O método Subscribe do provedor retorna uma implementação de IDisposable que
permite que os observadores interrompam o recebimento de notificações antes que
o método OnCompleted seja chamado. O código-fonte para essa
classe Unsubscriber(Of BaggageInfo) é mostrado no exemplo a seguir. Quando
a classe é instanciada no método BaggageHandler.Subscribe, ela é passado
como referência para a coleção observers e uma referência para o observador que
é adicionado à coleção. Essas referências são atribuídas a variáveis locais. Quando
o método Dispose do objeto é chamado, ele verifica se o observador ainda existe
na coleção observers e, em caso afirmativo, remove o observador.
C#Copiar
internal class Unsubscriber<BaggageInfo> : IDisposable
{
private List<IObserver<BaggageInfo>> _observers;
private IObserver<BaggageInfo> _observer;
internal Unsubscriber(List<IObserver<BaggageInfo>> observers,
IObserver<BaggageInfo> observer)
{
this._observers = observers;
this._observer = observer;
}
public void Dispose()
{
if (_observers.Contains(_observer))
_observers.Remove(_observer);
}
}
O exemplo a seguir fornece uma implementação
de IObserver<T> denominada ArrivalsMonitor, que é uma classe base que exibe
informações de coleta de bagagem. As informações são exibidas em ordem
alfabética, pelo nome da cidade de origem. Os métodos de ArrivalsMonitor são
marcados como overridable (no Visual Basic) ou virtual(em C#), portanto, todos
podem ser substituídos por uma classe derivada.
Página 14 de Padrao Observer
podem ser substituídos por uma classe derivada.
C#Copiar
using System;
using System.Collections.Generic;
public class ArrivalsMonitor : IObserver<BaggageInfo>
{
private string name;
private List<string> flightInfos = new List<string>();
private IDisposable cancellation;
private string fmt = "{0,-20} {1,5} {2, 3}";
public ArrivalsMonitor(string name)
{
if (String.IsNullOrEmpty(name))
throw new ArgumentNullException("The observer must be assigned a
name.");
this.name = name;
}
public virtual void Subscribe(BaggageHandler provider)
{
cancellation = provider.Subscribe(this);
}
public virtual void Unsubscribe()
{
cancellation.Dispose();
flightInfos.Clear();
}
public virtual void OnCompleted()
{
flightInfos.Clear();
}
// No implementation needed: Method is not called by the BaggageHandler
class.
public virtual void OnError(Exception e)
{
// No implementation.
}
// Update information.
public virtual void OnNext(BaggageInfo info)
{
bool updated = false;
// Flight has unloaded its baggage; remove from the monitor.
if (info.Carousel == 0) {
var flightsToRemove = new List<string>();
string flightNo = String.Format("{0,5}", info.FlightNumber);
foreach (var flightInfo in flightInfos) {
if (flightInfo.Substring(21, 5).Equals(flightNo)){
flightsToRemove.Add(flightInfo);
updated = true;
}
}
foreach (var flightToRemove in flightsToRemove)
flightInfos.Remove(flightToRemove);
flightsToRemove.Clear();
}
Página 15 de Padrao Observer
}
else {
// Add flight if it does not exist in the collection.
string flightInfo = String.Format(fmt, info.From,
info.FlightNumber, info.Carousel);
if (! flightInfos.Contains(flightInfo)) {
flightInfos.Add(flightInfo);
updated = true;
}
}
if (updated) {
flightInfos.Sort();
Console.WriteLine("Arrivals information from {0}", this.name);
foreach (var flightInfo in flightInfos)
Console.WriteLine(flightInfo);
Console.WriteLine();
}
}
}
A classe ArrivalsMonitor inclui os métodos Subscribe e Unsubscribe. O
método Subscribe permite que a classe salve a implementação
de IDisposable retornada pela chamada para Subscribe para uma variável
particular. O método Unsubscribe permite que a classe cancele a assinatura de
notificações ao chamar a implementação de Dispose do
provedor. ArrivalsMonitor também fornece implementações dos
métodos OnNext, OnError, e OnCompleted. Somente a implementação
de OnNext contém uma quantidade significativa de código. O método funciona com
um objeto List<T> particular, classificado e genérico que mantém informações sobre
os aeroportos de origem dos voos que chegam e sobre as esteiras nas quais as
respectivas bagagens estarão disponíveis. Se a classe BaggageHandler relata um
novo voo chegando, a implementação do método OnNext adiciona informações
sobre esse voo à lista. Se a classe BaggageHandler relata que a bagagem do voo
foi descarregada, o método OnNextremove essa voo da lista. Sempre que uma
alteração é feita, a lista é classificada e exibida no console.
O exemplo a seguir contém o ponto de entrada do aplicativo que instancia a
classe BaggageHandler, bem como duas instâncias da classe ArrivalsMonitor e
usa o método BaggageHandler.BaggageStatus para adicionar e remover
informações sobre voos que chegam. Em cada caso, os observadores recebem
atualizações e exibem corretamente as informações de coleta de bagagem.
C#Copiar
using System;
using System.Collections.Generic;
public class Example
{
public static void Main()
{
BaggageHandler provider = new BaggageHandler();
ArrivalsMonitor observer1 = new
ArrivalsMonitor("BaggageClaimMonitor1");
ArrivalsMonitor observer2 = new ArrivalsMonitor("SecurityExit");
provider.BaggageStatus(712, "Detroit", 3);
observer1.Subscribe(provider);
provider.BaggageStatus(712, "Kalamazoo", 3);
provider.BaggageStatus(400, "New York-Kennedy", 1);
provider.BaggageStatus(712, "Detroit", 3);
Página 16 de Padrao Observer
provider.BaggageStatus(712, "Detroit", 3);
observer2.Subscribe(provider);
provider.BaggageStatus(511, "San Francisco", 2);
provider.BaggageStatus(712);
observer2.Unsubscribe();
provider.BaggageStatus(400);
provider.LastBaggageClaimed();
}
}
// The example displays the following output:
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
//
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
// Kalamazoo 712 3
//
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
//
// Arrivals information from SecurityExit
// Detroit 712 3
//
// Arrivals information from SecurityExit
// Detroit 712 3
// Kalamazoo 712 3
//
// Arrivals information from SecurityExit
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
//
// Arrivals information from BaggageClaimMonitor1
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from SecurityExit
// Detroit 712 3
// Kalamazoo 712 3
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from BaggageClaimMonitor1
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from SecurityExit
// New York-Kennedy 400 1
// San Francisco 511 2
//
// Arrivals information from BaggageClaimMonitor1
// San Francisco 511 2
Página 17 de Padrao Observer
// San Francisco 511 2
Tópicos relacionados
Título Descrição
Práticas recomendadas para o
padrão de design do observador
São descritas as práticas recomendadas ao desenvolver
aplicativos que implementam o padrão de design do
observador.
Como implementar um provedor É fornecida uma implementação passo a passo de um provedor
para uma aplicativo de monitoramento de temperatura.
Como implementar um
observador
É fornecida uma implementação passo a passo de um
observador para uma aplicativo de monitoramento de
temperatura.
De <https://docs.microsoft.com/pt-br/dotnet/standard/events/observer-design-pattern>
Página 18 de Padrao Observer
Práticas recomendadas para o padrão de design
do observador
30/03/2017•
6 minutos para ler•
Colaboradores•
No .NET Framework, 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étodoOnError 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
terça-feira, 24 de abril de 2018 10:04
Página 19 de Padrao Observer
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 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.
•
O provedor deve seguir essas práticas recomendadas ao manipular exceções e
chamar o método OnError:
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 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.
•
O observador deve seguir essas práticas recomendadas ao responder a uma
chamada de método OnError de um provedor:
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>.
Consulte também
Padrão de design do observador
Como implementar um observador
Página 20 de Padrao Observer
Como implementar um observador
Como implementar um provedor
De <https://docs.microsoft.com/pt-br/dotnet/standard/events/observer-design-pattern-best-practices>
Página 21 de Padrao Observer
Como implementar um provedor
30/03/2017•
9 minutos para ler•
Colaboradores•
O padrão de design do observador requer uma divisão entre um provedor, que
monitora os dados e envia notificações e um ou mais observadores, que recebem
notificações (retornos de chamada) do provedor. Este tópico discute como criar um
provedor. Um tópico relacionado, Como implementar um observador, descreve
como criar um observador.
Defina os dados que o provedor é responsável por enviar para os
observadores. Embora o provedor e os dados que ele envia para observadores
possam ser um tipo único, geralmente são representados por tipos
diferentes.Por exemplo, em um aplicativo de monitoramento de temperatura, a
estrutura Temperature define os dados que o provedor (que é representado
pela classe TemperatureMonitor definida na próxima etapa) monitora e quais
observadores assinar.
C#Copiar
using System;
public struct Temperature
{
private decimal temp;
private DateTime tempDate;
public Temperature(decimal temperature, DateTime dateAndTime)
{
this.temp = temperature;
this.tempDate = dateAndTime;
}
public decimal Degrees
{ get { return this.temp; } }
public DateTime Date
{ get { return this.tempDate; } }
}
1.
Defina o provedor de dados, que é um tipo que implementa a
interface System.IObservable<T>. O argumento de tipo genérico do provedor é
o tipo que o provedor envia a observadores. O exemplo a seguir define uma
classe TemperatureMonitor, que é uma implementação
construída System.IObservable<T> com um argumento de tipo genérico
de Temperature.
C#Copiar
using System;
2.
Para criar um provedor
terça-feira, 24 de abril de 2018 10:04
Página 22 de Padrao Observer
using System;
using System.Collections.Generic;
public class TemperatureMonitor : IObservable<Temperature>
{
Determine como o provedor armazena as referências a observadores para que
cada observador possa ser notificado quando apropriado. Normalmente, um
objeto de coleção como um objeto genérico List<T> é usado para essa
finalidade. O exemplo a seguir define um objeto particular List<T> que é
instanciado no constructo de classe TemperatureMonitor.
C#Copiar
using System;
using System.Collections.Generic;
public class TemperatureMonitor : IObservable<Temperature>
{
List<IObserver<Temperature>> observers;
public TemperatureMonitor()
{
observers = new List<IObserver<Temperature>>();
}
3.
Defina uma implementação IDisposable que o provedor possa retornar para os
assinantes para que eles possam interromper o recebimento de notificações a
qualquer momento. O exemplo a seguir define uma
classe Unsubscriber aninhada para a qual é passada uma referência à
coleção de assinantes e ao assinante quando a classe é instanciada. Esse
código permite que o assinante chame a
implementação IDisposable.Dispose do objeto para se remover da coleção de
assinantes.
C#Copiar
private class Unsubscriber : IDisposable
{
private List<IObserver<Temperature>> _observers;
private IObserver<Temperature> _observer;
public Unsubscriber(List<IObserver<Temperature>> observers,
IObserver<Temperature> observer)
{
this._observers = observers;
this._observer = observer;
}
public void Dispose()
{
if (! (_observer == null)) _observers.Remove(_observer);
}
}
4.
Implementar o método de IObservable<T>.Subscribe . O método recebe uma
referência para a interface System.IObserver<T> e deve ser armazenado no
objeto criado para essa finalidade na etapa 3. O método deve retornar a
implementação IDisposable desenvolvida na etapa 4. O exemplo a seguir
mostra a implementação do método Subscribe na
classe TemperatureMonitor.
C#Copiar
public IDisposable Subscribe(IObserver<Temperature> observer)
5.
Página 23 de Padrao Observer
public IDisposable Subscribe(IObserver<Temperature> observer)
{
if (! observers.Contains(observer))
observers.Add(observer);
return new Unsubscriber(observers, observer);
}
Notificar os observadores conforme apropriado, chamando as
implementações IObserver<T>.OnNext, IObserver<T>.OnErrore IObserver<T>
.OnCompleted. Em alguns casos, um provedor não pode chamar o
método OnError quando ocorre um erro. Por exemplo, o
método GetTemperature a seguir simula um monitor que lê dados de
temperatura a cada cinco segundos e notifica os observadores se a
temperatura foi alterada em pelo menos 0,1 grau desde a leitura anterior. Se o
dispositivo não relatar uma temperatura (ou seja, se o valor for nulo), o
provedor notificará observadores de que a transmissão foi concluída. Observe
que, além de chamar o método OnCompleted de cada observador, o
método GetTemperature limpa a coleção List<T>.Nesse caso, o provedor não
torna nenhuma chamada para o método OnError de seus observadores.
C#Copiar
public void GetTemperature()
{
// Create an array of sample data to mimic a temperature
device.
Nullable<Decimal>[] temps = {14.6m, 14.65m, 14.7m, 14.9m,
14.9m, 15.2m, 15.25m, 15.2m,
15.4m, 15.45m, null };
// Store the previous temperature, so notification is only
sent after at least .1 change.
Nullable<Decimal> previous = null;
bool start = true;
foreach (var temp in temps) {
System.Threading.Thread.Sleep(2500);
if (temp.HasValue) {
if (start || (Math.Abs(temp.Value - previous.Value) >=
0.1m )) {
Temperature tempData = new Temperature(temp.Value,
DateTime.Now);
foreach (var observer in observers)
observer.OnNext(tempData);
previous = temp;
if (start) start = false;
}
}
else {
foreach (var observer in observers.ToArray())
if (observer != null) observer.OnCompleted();
observers.Clear();
break;
}
}
}
6.
Página 24 de Padrao Observer
Exemplo
O exemplo a seguir contém o código-fonte completo para definir uma
implementação IObservable<T> para uma aplicativo de monitoramento de
temperatura. Ele inclui a estrutura Temperature, os dados enviados para
observadores e a classe TemperatureMonitor, que é a
implementação IObservable<T>.
C#Copiar
using System.Threading;
using System;
using System.Collections.Generic;
public class TemperatureMonitor : IObservable<Temperature>
{
List<IObserver<Temperature>> observers;
public TemperatureMonitor()
{
observers = new List<IObserver<Temperature>>();
}
private class Unsubscriber : IDisposable
{
private List<IObserver<Temperature>> _observers;
private IObserver<Temperature> _observer;
public Unsubscriber(List<IObserver<Temperature>> observers,
IObserver<Temperature> observer)
{
this._observers = observers;
this._observer = observer;
}
public void Dispose()
{
if (! (_observer == null)) _observers.Remove(_observer);
}
}
public IDisposable Subscribe(IObserver<Temperature> observer)
{
if (! observers.Contains(observer))
observers.Add(observer);
return new Unsubscriber(observers, observer);
}
public void GetTemperature()
{
// Create an array of sample data to mimic a temperature device.
Nullable<Decimal>[] temps = {14.6m, 14.65m, 14.7m, 14.9m, 14.9m,
15.2m, 15.25m, 15.2m,
15.4m, 15.45m, null };
// Store the previous temperature, so notification is only sent
after at least .1 change.
Nullable<Decimal> previous = null;
bool start = true;
foreach (var temp in temps) {
System.Threading.Thread.Sleep(2500);
if (temp.HasValue) {
if (start || (Math.Abs(temp.Value - previous.Value) >= 0.1m ))
{
Temperature tempData = new Temperature(temp.Value,
Página 25 de Padrao Observer
Temperature tempData = new Temperature(temp.Value,
DateTime.Now);
foreach (var observer in observers)
observer.OnNext(tempData);
previous = temp;
if (start) start = false;
}
}
else {
foreach (var observer in observers.ToArray())
if (observer != null) observer.OnCompleted();
observers.Clear();
break;
}
}
}
}
Consulte também
IObservable<T>
Padrão de design do observador
Como implementar um observador
Práticas recomendadas para o padrão de design do observador
De <https://docs.microsoft.com/pt-br/dotnet/standard/events/how-to-implement-a-provider>
Página 26 de Padrao Observer
Como implementar um observador
30/03/2017•
5 minutos para ler•
Colaboradores•
O padrão de design do observador exige uma divisão entre um observador, que
registra as notificações, e um provedor, que monitora os dados e envia notificações
e um ou mais observadores. Este tópico discute como criar um observador. Um
tópico relacionado, Como implementar um provedor, descreve como criar um
provedor.
Defina o observador, que é um tipo que implementa a
interface System.IObserver<T>. Por exemplo, o código a seguir define um tipo
chamado TemperatureReporter, que é uma
implementação System.IObserver<T>construída com um argumento de tipo
genérico de Temperature.
C#Copiar
public class TemperatureReporter : IObserver<Temperature>
1.
Se o observador puder interromper o recebimento de notificações antes de o
provedor chamar sua implementação de IObserver<T>.OnCompleted, defina
uma variável privada que armazenará a implementação
de IDisposable retornada pelo método IObservable<T>.Subscribe do
provedor. Você também deve definir um método de assinatura que chama o
método Subscribe do provedor e armazena o objeto IDisposable retornado.Por
exemplo, o código a seguir define uma variável privada
chamada unsubscriber, e define um método Subscribe que chama o
método Subscribe do provedor e atribui o objeto retornado à
variável unsubscriber.
C#Copiar
public class TemperatureReporter : IObserver<Temperature>
{
private IDisposable unsubscriber;
private bool first = true;
private Temperature last;
public virtual void Subscribe(IObservable<Temperature> provider)
{
unsubscriber = provider.Subscribe(this);
}
2.
Defina um método que permite ao observador interromper o recebimento de
notificações antes de o provedor chamar sua implementação
de IObserver<T>.OnCompleted, se esse recurso for necessário. O exemplo a
seguir define um método Unsubscribe.
C#Copiar
public virtual void Unsubscribe()
{
3.
Para criar um observador
terça-feira, 24 de abril de 2018 10:06
Página 27 de Padrao Observer
{
unsubscriber.Dispose();
}
Forneça implementações dos três métodos definidos pela
interface IObserver<T>: IObserver<T>.OnNext, IObserver<T>.OnError e IObser
ver<T>.OnCompleted. Dependendo do provedor e das necessidades do
aplicativo, os métodos OnError e OnCompleted podem ser implementações de
stub. Observe que o método OnError não deve tratar do
objeto Exception transmitido como uma exceção, e o método OnCompleted é
livre para chamar a implementação IDisposable.Dispose do provedor. O
exemplo a seguir mostra a implementação IObserver<T> da
classe TemperatureReporter.
C#Copiar
public virtual void OnCompleted()
{
Console.WriteLine("Additional temperature data will not be
transmitted.");
}
public virtual void OnError(Exception error)
{
// Do nothing.
}
public virtual void OnNext(Temperature value)
{
Console.WriteLine("The temperature is {0}°C at {1:g}",
value.Degrees, value.Date);
if (first)
{
last = value;
first = false;
}
else
{
Console.WriteLine(" Change: {0}° in {1:g}",
value.Degrees - last.Degrees,value.Date.ToUniversalTime() - last.Date.ToUniversalTime());
}
}
4.
Exemplo
O exemplo a seguir contém o código-fonte completo para a
classe TemperatureReporter, que fornece a implementação IObserver<T> para
uma aplicativo de monitoramento de temperatura.
C#Copiar
public class TemperatureReporter : IObserver<Temperature>
{
private IDisposable unsubscriber;
private bool first = true;
private Temperature last;
public virtual void Subscribe(IObservable<Temperature> provider)
{
Página 28 de Padrao Observer
{
unsubscriber = provider.Subscribe(this);
}
public virtual void Unsubscribe()
{
unsubscriber.Dispose();
}
public virtual void OnCompleted()
{
Console.WriteLine("Additional temperature data will not be
transmitted.");
}
public virtual void OnError(Exception error)
{
// Do nothing.
}
public virtual void OnNext(Temperature value)
{
Console.WriteLine("The temperature is {0}°C at {1:g}",
value.Degrees, value.Date);
if (first)
{
last = value;
first = false;
}
else
{
Console.WriteLine(" Change: {0}° in {1:g}", value.Degrees -
last.Degrees,
value.Date.ToUniversalTime() - last.Date.ToUniversalTime());
}
}
}
Consulte também
IObserver<T>
Padrão de design do observador
Como implementar um provedor
Práticas recomendadas para o padrão de design do observador
De <https://docs.microsoft.com/pt-br/dotnet/standard/events/how-to-implement-an-observer>
Página 29 de Padrao Observer
IObserver<T> Interface
Definition
Namespace:
System
Assemblies:
System.Runtime.dll, mscorlib.dll, netstandard.dll
Provides a mechanism for receiving push-based notifications.
C#Copiar
public interface IObserver<in T>
Type Parameters
T
The object that provides notification information.
Derived
System.Web.Hosting.LowPhysicalMemoryObserver
System.Web.Hosting.RecycleLimitObserver
Examples
The following example illustrates the observer design pattern. It defines
a Location class that contains latitude and longitude information.
C#Copiar
public struct Location
{
double lat, lon;
public Location(double latitude, double longitude)
{
this.lat = latitude;
this.lon = longitude;
}
public double Latitude
{ get { return this.lat; } }
public double Longitude
{ get { return this.lon; } }
}
The LocationReporter class provides the IObserver<T> implementation. It displays
information about the current location to the console. Its constructor includes
a name parameter, which allows the LocationReporter instance to identify itself in
its string output. It also includes a Subscribe method, which wraps a call to the
provider's Subscribemethod. This enables the method to assign the
returned IDisposable reference to a private variable. The LocationReporter class
also includes an Unsubscribe method, which calls the IDisposable.Dispose method
of the object returned by the IObservable<T>.Subscribe method. The following code
defines the LocationReporter class.
C#Copiar
using System;
public class LocationReporter : IObserver<Location>
terça-feira, 24 de abril de 2018 10:07
Página 30 de Padrao Observer
public class LocationReporter : IObserver<Location>
{
private IDisposable unsubscriber;
private string instName;
public LocationReporter(string name)
{
this.instName = name;
}
public string Name
{ get{ return this.instName; } }
public virtual void Subscribe(IObservable<Location> provider)
{
if (provider != null)
unsubscriber = provider.Subscribe(this);
}
public virtual void OnCompleted()
{
Console.WriteLine("The Location Tracker has completed transmitting
data to {0}.", this.Name);
this.Unsubscribe();
}
public virtual void OnError(Exception e)
{
Console.WriteLine("{0}: The location cannot be determined.",
this.Name);
}
public virtual void OnNext(Location value)
{
Console.WriteLine("{2}: The current location is {0}, {1}",
value.Latitude, value.Longitude, this.Name);
}
public virtual void Unsubscribe()
{
unsubscriber.Dispose();
}
}
The LocationTracker class provides the IObservable<T> implementation.
Its TrackLocation method is passed a nullable Location object that contains the
latitude and longitude data. If the Location value is not null,
the TrackLocation method calls the OnNext method of each observer.
C#Copiar
public class LocationTracker : IObservable<Location>
{
public LocationTracker()
{
observers = new List<IObserver<Location>>();
}
private List<IObserver<Location>> observers;
public IDisposable Subscribe(IObserver<Location> observer)
{
if (! observers.Contains(observer))
observers.Add(observer);
return new Unsubscriber(observers, observer);
}
private class Unsubscriber : IDisposable
Página 31 de Padrao Observer
private class Unsubscriber : IDisposable
{
private List<IObserver<Location>>_observers;
private IObserver<Location> _observer;
public Unsubscriber(List<IObserver<Location>> observers,
IObserver<Location> observer)
{
this._observers = observers;
this._observer = observer;
}
public void Dispose()
{
if (_observer != null && _observers.Contains(_observer))
_observers.Remove(_observer);
}
}
public void TrackLocation(Nullable<Location> loc)
{
foreach (var observer in observers) {
if (! loc.HasValue)
observer.OnError(new LocationUnknownException());
else
observer.OnNext(loc.Value);
}
}
public void EndTransmission()
{
foreach (var observer in observers.ToArray())
if (observers.Contains(observer))
observer.OnCompleted();
observers.Clear();
}
}
If the Location value is null, the TrackLocation method instantiates
a LocationNotFoundException object, which is shown in the following example. It
then calls each observer's OnError method and passes it
the LocationNotFoundException object. Note
that LocationNotFoundException derives from Exception but does not add any
new members.
C#Copiar
public class LocationUnknownException : Exception
{
internal LocationUnknownException()
{ }
}
Observers register to receive notifications from a TrackLocation object by calling
its IObservable<T>.Subscribemethod, which assigns a reference to the observer
object to a private generic List<T> object. The method returns
an Unsubscriber object, which is an IDisposable implementation that enables
observers to stop receiving notifications. The LocationTracker class also includes
an EndTransmission method. When no further location data is available, the
method calls each observer's OnCompleted method and then clears the internal list
of observers.
The following code then instantiates the provider and the observer.
C#Copiar
Página 32 de Padrao Observer
C#Copiar
using System;
class Program
{
static void Main(string[] args)
{
// Define a provider and two observers.
LocationTracker provider = new LocationTracker();
LocationReporter reporter1 = new LocationReporter("FixedGPS");
reporter1.Subscribe(provider);
LocationReporter reporter2 = new LocationReporter("MobileGPS");
reporter2.Subscribe(provider);
provider.TrackLocation(newLocation(47.6456, -122.1312));
reporter1.Unsubscribe();
provider.TrackLocation(new Location(47.6677, -122.1199));
provider.TrackLocation(null);
provider.EndTransmission();
}
}
// The example displays output similar to the following:
// FixedGPS: The current location is 47.6456, -122.1312
// MobileGPS: The current location is 47.6456, -122.1312
// MobileGPS: The current location is 47.6677, -122.1199
// MobileGPS: The location cannot be determined.
// The Location Tracker has completed transmitting data to MobileGPS.
Remarks
The IObserver<T> and IObservable<T> interfaces provide a generalized mechanism
for push-based notification, also known as the observer design pattern.
The IObservable<T> interface represents the class that sends notifications (the
provider); the IObserver<T> interface represents the class that receives them (the
observer). T represents the class that provides the notification information.
An IObserver<T> implementation arranges to receive notifications from a provider
(an IObservable<T>implementation) by passing an instance of itself to the
provider's IObservable<T>.Subscribe method. This method returns
an IDisposable object that can be used to unsubscribe the observer before the
provider finishes sending notifications.
The OnNext method, which is typically called by the provider to supply the
observer with new data or state information.
•
The OnError method, which is typically called by the provider to indicate that
data is unavailable, inaccessible, or corrupted, or that the provider has
experienced some other error condition.
•
The OnCompleted method, which is typically called by the provider to indicate
that it has finished sending notifications to observers.
•
The IObserver<T> interface defines the following three methods that the observer
must implement:
Methods
OnCompleted() Notifies the observer that the provider has finished sending push-based
notifications.
OnError(Exceptio
n)
Notifies the observer that the provider has experienced an error
condition.
Página 33 de Padrao Observer
OnNext(T) Provides the observer with new data.
Applies to
.NET Core
2.1 Preview 2, 2.0, 1.1, 1.0
.NET Framework
4.7.2, 4.7.1, 4.7, 4.6.2, 4.6.1, 4.6, 4.5.2, 4.5.1, 4.5
.NET Standard
2.0, 1.6, 1.5, 1.4, 1.3, 1.2, 1.1, 1.0
Xamarin.Android
7.1
Xamarin.iOS
10.8
Xamarin.Mac
3.0
IObservable<T>•
See Also
De <https://docs.microsoft.com/pt-br/dotnet/api/system.iobserver-1?view=netframework-4.7.2>
=================================================================
TRADUÇÃO
IObserver <T> interface
Definição
Namespace:
Sistema
Assembléias:
System.Runtime.dll, mscorlib.dll, netstandard.dll
Fornece um mecanismo para receber notificações baseadas em push.
C #Copiar
public interface IObserver<in T>
Parâmetros de tipo
T
O objeto que fornece informações de notificação.
Derivado
Sistema. Rede. Hospedagem. Observador de baixa memória física
Sistema. Rede. Hospedagem. Observador de Limite de Reciclagem
Exemplos
Página 34 de Padrao Observer
Exemplos
O exemplo a seguir ilustra o padrão de design do observador. Define
uma Locationclasse que contém informações sobre latitude e longitude.
C #Copiar
public struct Location
{
double lat, lon;
public Location(double latitude, double longitude)
{
this.lat = latitude;
this.lon = longitude;
}
public double Latitude
{ get { return this.lat; } }
public double Longitude
{ get { return this.lon; } }
}
A LocationReporterclasse fornece a implementação do IObserver <T> . Ele exibe
informações sobre o local atual no console. Seu construtor inclui um nameparâmetro,
que permite que a LocationReporterinstância se identifique em sua saída de
cadeia. Ele também inclui um Subscribemétodo, que envolve uma chamada para
o método de inscrição do provedor . Isso permite que o método atribua
a referência IDisposable retornada a uma variável
privada. A LocationReporterclasse também inclui um Unsubscribemétodo, que
chama o método IDisposable.Dispose do objeto retornado pelo método IObservable
<T> .Subscribe . O código a seguir define a LocationReporterclasse.
C #Copiar
using System;
public class LocationReporter : IObserver<Location>
{
private IDisposable unsubscriber;
private string instName;
public LocationReporter(string name)
{
this.instName = name;
}
public string Name
{ get{ return this.instName; } }
public virtual void Subscribe(IObservable<Location> provider)
{
if (provider != null)
unsubscriber = provider.Subscribe(this);
}
public virtual void OnCompleted()
{
Console.WriteLine("The Location Tracker has completed transmitting
data to {0}.", this.Name);
this.Unsubscribe();
}
public virtual void OnError(Exception e)
{
Console.WriteLine("{0}: The location cannot be determined.",
this.Name);
Página 35 de Padrao Observer
this.Name);
}
public virtual void OnNext(Location value)
{
Console.WriteLine("{2}: The current location is {0}, {1}",
value.Latitude, value.Longitude, this.Name);
}
public virtual void Unsubscribe()
{
unsubscriber.Dispose();
}
}
A LocationTrackerclasse fornece a implementação IObservable
<T> . Seu TrackLocationmétodo é passado um Locationobjeto anulável que
contém os dados de latitude e longitude. Se o Locationvalor não for null,
o TrackLocationmétodo chama o método OnNext de cada observador.
C #Copiar
public class LocationTracker : IObservable<Location>
{
public LocationTracker()
{
observers = new List<IObserver<Location>>();
}
private List<IObserver<Location>> observers;
public IDisposable Subscribe(IObserver<Location> observer)
{
if (! observers.Contains(observer))
observers.Add(observer);
return new Unsubscriber(observers, observer);
}
private class Unsubscriber : IDisposable
{
private List<IObserver<Location>>_observers;
private IObserver<Location> _observer;
public Unsubscriber(List<IObserver<Location>> observers,
IObserver<Location> observer)
{
this._observers = observers;
this._observer = observer;
}
public void Dispose()
{
if (_observer != null && _observers.Contains(_observer))
_observers.Remove(_observer);
}
}
public void TrackLocation(Nullable<Location> loc)
{
foreach (var observer in observers) {
if (! loc.HasValue)
observer.OnError(new LocationUnknownException());
else
observer.OnNext(loc.Value);
}
}
Página 36 de Padrao Observer
}
public void EndTransmission()
{
foreach (var observer in observers.ToArray())
if (observers.Contains(observer))
observer.OnCompleted();
observers.Clear();
}
}
Se o Locationvalor for null, o TrackLocationmétodo instancia
um LocationNotFoundExceptionobjeto, mostrado no exemplo a seguir. Em
seguida, ele chama o método OnError de cada observador e passa
o LocationNotFoundExceptionobjeto para ele. Observe
que LocationNotFoundExceptionderiva da exceção, mas não adiciona novos
membros.
C #Copiar
public class LocationUnknownException : Exception
{
internal LocationUnknownException()
{ }
}
Observadores registram para receber notificações de um TrackLocationobjeto
chamando seu método IObservable <T> .Subscribe , que atribui uma referência ao
objeto observador a um objeto List <T> genérico particular . O métodoretorna
um Unsubscriberobjeto, que é uma implementação descartável que permite que os
observadores parem de receber notificações. A LocationTrackerclasse também
inclui um EndTransmissionmétodo. Quando não há mais dados de localização
disponíveis, o método chama o método OnCompleted de cada observador e, em
seguida, limpa a lista interna de observadores.
O código a seguir instancia o provedor e o observador.
C #Copiar
using System;
class Program
{
static void Main(string[] args)
{
// Define a provider and two observers.
LocationTracker provider = new LocationTracker();
LocationReporter reporter1 = new LocationReporter("FixedGPS");
reporter1.Subscribe(provider);
LocationReporter reporter2 = new LocationReporter("MobileGPS");
reporter2.Subscribe(provider);
provider.TrackLocation(new Location(47.6456, -122.1312));
reporter1.Unsubscribe();
provider.TrackLocation(new Location(47.6677, -122.1199));
provider.TrackLocation(null);
provider.EndTransmission();
}
}
// The example displays output similar to the following:
// FixedGPS: The current location is 47.6456, -122.1312
// MobileGPS: The current location is 47.6456, -122.1312
// MobileGPS: The current location is 47.6677, -122.1199
// MobileGPS: The location cannot be determined.
// The Location Tracker has completed transmitting data to MobileGPS.
Página 37 de Padrao Observer
// The Location Tracker has completed transmitting data to MobileGPS.
Observações
As interfaces IObserver <T> e IObservable <T> fornecem um mecanismo
generalizado para notificação baseada em push, também conhecido como padrão
de design do observador. A interface IObservable <T> representa a classe que
envia notificações (o provedor); a interface IObserver <T> representa a classe que
os recebe (o observador). Trepresenta a classe que fornece as informações de
notificação.
Uma implementação IObserver <T> organiza para receber notificações de um
provedor (uma implementação IObservable <T> ) passando uma instância de si
mesmo para o método IObservable <T> .Subscribe do provedor . Esse método
retorna um objeto IDisposable que pode ser usado para cancelar a assinatura do
observador antes que o provedor termine o envio de notificações.
O método OnNext , que é normalmente chamado pelo provedor para fornecer
ao observador novos dados ou informações de estado.
•
O método OnError , que é normalmente chamado pelo provedor para indicar
que os dados estão indisponíveis, inacessíveis ou corrompidos, ou que o
provedor experimentou alguma outra condição de erro.
•
O método OnCompleted , que é normalmente chamado pelo provedor para
indicar que ele concluiu o envio de notificações para os observadores.
•
A interface IObserver <T> define os três métodos a seguir que o observador deve
implementar:
Métodos
Em concluído () Notifica o observador que o provedor terminou o envio de notificações
baseadas em push.
Em erro
(exceção)
Notifica o observador que o provedor teve uma condição de erro.
Na próxima (T) Fornece o observador com novos dados.
Aplica-se a
.NET Core
2.1 Visualização 2 , 2.0 , 1.1 , 1.0
.NET Framework
4.7.2 , 4.7.1 , 4.7 , 4.6.2 , 4.6.1 , 4.6 , 4.5.2 , 4.5.1 , 4.5
Padrão .NET
2,0 , 1,6 , 1,5 , 1,4 , 1,3 , 1,2 , 1,1 , 1,0
Xamarin.Android
7,1
Xamarin.iOS
10,8
Xamarin.Mac
3,0
Página 38 de Padrao Observer
3,0
IObservable <T>•
Veja também
De <https://docs.microsoft.com/pt-br/dotnet/api/system.iobserver-1?view=netframework-4.7.2>
Página 39 de Padrao Observer
IObservable<T> Interface
Definition
Namespace:
System
Assemblies:
System.Runtime.dll, mscorlib.dll, netstandard.dll
Defines a provider for push-based notification.
C#Copiar
public interface IObservable<out T>
Type Parameters
T
The object that provides notification information.
Derived
System.Diagnostics.DiagnosticListener
System.Web.Hosting.AspNetMemoryMonitor
System.Web.Hosting.AspNetMemoryMonitor
Examples
The following example illustrates the observer design pattern. It defines
a Location class that contains latitude and longitude information.
C#Copiar
public struct Location
{
double lat, lon;
public Location(double latitude, double longitude)
{
this.lat = latitude;
this.lon = longitude;
}
public double Latitude
{ get { return this.lat; } }
public double Longitude
{ get { return this.lon; } }
}
The LocationTracker class provides the IObservable<T> implementation.
Its TrackLocation method is passed a nullable Location object that contains the
latitude and longitude data. If the Location value is not null,
the TrackLocation method calls the OnNext method of each observer.
C#Copiar
public class LocationTracker : IObservable<Location>
{
public LocationTracker()
{
observers = new List<IObserver<Location>>();
}
terça-feira, 24 de abril de 2018 10:08
Página 40 de Padrao Observer
}
private List<IObserver<Location>> observers;
public IDisposable Subscribe(IObserver<Location> observer)
{
if (! observers.Contains(observer))
observers.Add(observer);
return new Unsubscriber(observers, observer);
}
private class Unsubscriber : IDisposable
{
private List<IObserver<Location>>_observers;
private IObserver<Location> _observer;
public Unsubscriber(List<IObserver<Location>> observers,
IObserver<Location> observer)
{
this._observers = observers;
this._observer = observer;
}
public void Dispose()
{
if (_observer != null && _observers.Contains(_observer))
_observers.Remove(_observer);
}
}
public void TrackLocation(Nullable<Location> loc)
{
foreach (var observer in observers) {
if (! loc.HasValue)
observer.OnError(new LocationUnknownException());
else
observer.OnNext(loc.Value);
}
}
public void EndTransmission()
{
foreach (var observer in observers.ToArray())
if (observers.Contains(observer))
observer.OnCompleted();
observers.Clear();
}
}
If the Location value is null, the TrackLocation method instantiates
a LocationUnknownException object, which is shown in the following example. It
then calls each observer's OnError method and passes it
the LocationUnknownException object. Note
that LocationUnknownException derives from Exception, but does not add any new
members.
C#Copiar
public class LocationUnknownException : Exception
{
internal LocationUnknownException()
{ }
}
Observers register to receive notifications from a TrackLocation object by calling
its IObservable<T>.Subscribemethod, which assigns a reference to the observer
Página 41 de Padrao Observer
its IObservable<T>.Subscribemethod, which assigns a reference to the observer
object to a private generic List<T> object. The method returns
an Unsubscriber object, which is an IDisposable implementation that enables
observers to stop receiving notifications. The LocationTracker class also includes
an EndTransmission method. When no further location data is available, the
method calls each observer's OnCompleted method and then clears the internal list
of observers.
In this example, the LocationReporter class provides
the IObserver<T> implementation. It displays information about the current location
to the console. Its constructor includes a name parameter, which enables
the LocationReporter instance to identify itself in its string output. It also includesa Subscribe method, which wraps a call to the provider's Subscribe method. This
allows the method to assign the returned IDisposable reference to a private variable.
The LocationReporter class also includes an Unsubscribe method, which calls
the IDisposable.Dispose method of the object that is returned by
the IObservable<T>.Subscribe method. The following code defines
the LocationReporter class.
C#Copiar
using System;
public class LocationReporter : IObserver<Location>
{
private IDisposable unsubscriber;
private string instName;
public LocationReporter(string name)
{
this.instName = name;
}
public string Name
{ get{ return this.instName; } }
public virtual void Subscribe(IObservable<Location> provider)
{
if (provider != null)
unsubscriber = provider.Subscribe(this);
}
public virtual void OnCompleted()
{
Console.WriteLine("The Location Tracker has completed transmitting
data to {0}.", this.Name);
this.Unsubscribe();
}
public virtual void OnError(Exception e)
{
Console.WriteLine("{0}: The location cannot be determined.",
this.Name);
}
public virtual void OnNext(Location value)
{
Console.WriteLine("{2}: The current location is {0}, {1}",
value.Latitude, value.Longitude, this.Name);
}
public virtual void Unsubscribe()
{
unsubscriber.Dispose();
}
}
Página 42 de Padrao Observer
}
The following code then instantiates the provider and the observer.
C#Copiar
using System;
class Program
{
static void Main(string[] args)
{
// Define a provider and two observers.
LocationTracker provider = new LocationTracker();
LocationReporter reporter1 = new LocationReporter("FixedGPS");
reporter1.Subscribe(provider);
LocationReporter reporter2 = new LocationReporter("MobileGPS");
reporter2.Subscribe(provider);
provider.TrackLocation(new Location(47.6456, -122.1312));
reporter1.Unsubscribe();
provider.TrackLocation(new Location(47.6677, -122.1199));
provider.TrackLocation(null);
provider.EndTransmission();
}
}
// The example displays output similar to the following:
// FixedGPS: The current location is 47.6456, -122.1312
// MobileGPS: The current location is 47.6456, -122.1312
// MobileGPS: The current location is 47.6677, -122.1199
// MobileGPS: The location cannot be determined.
// The Location Tracker has completed transmitting data to MobileGPS.
Remarks
The IObserver<T> and IObservable<T> interfaces provide a generalized mechanism
for push-based notification, also known as the observer design pattern.
The IObservable<T> interface represents the class that sends notifications (the
provider); the IObserver<T> interface represents the class that receives them (the
observer). T represents the class that provides the notification information. In some
push-based notifications, the IObserver<T> implementation and T can represent the
same type.
The provider must implement a single method, Subscribe, that indicates that an
observer wants to receive push-based notifications. Callers to the method pass an
instance of the observer. The method returns an IDisposableimplementation that
enables observers to cancel notifications at any time before the provider has stopped
sending them.
At any given time, a given provider may have zero, one, or multiple observers. The
provider is responsible for storing references to observers and ensuring that they are
valid before it sends notifications. The IObservable<T> interface does not make any
assumptions about the number of observers or the order in which notifications are
sent.
The current data. The provider can call the IObserver<T>.OnNext method to
pass the observer a T object that has current data, changed data, or fresh data.
•
An error condition. The provider can call the IObserver<T>.OnError method to
notify the observer that some error condition has occurred.
•
No further data. The provider can call the IObserver<T>.OnCompleted method
to notify the observer that it has finished sending notifications.
•
The provider sends the following three kinds of notifications to the observer by
calling IObserver<T> methods:
Página 43 de Padrao Observer
to notify the observer that it has finished sending notifications.
Methods
Subscribe(IObserver<T>) Notifies the provider that an observer is to receive notifications.
Applies to
.NET Core
2.1 Preview 2, 2.0, 1.1, 1.0
.NET Framework
4.7.2, 4.7.1, 4.7, 4.6.2, 4.6.1, 4.6, 4.5.2, 4.5.1, 4.5
.NET Standard
2.0, 1.6, 1.5, 1.4, 1.3, 1.2, 1.1, 1.0
Xamarin.Android
7.1
Xamarin.iOS
10.8
Xamarin.Mac
3.0
IObserver<T>•
See Also
De <https://docs.microsoft.com/pt-br/dotnet/api/system.iobservable-1?view=netframework-4.7.2>
===================================================================
TRADUZIDO:
IObservable <T> Interface
Definição
Namespace:
Sistema
Assembléias:
System.Runtime.dll, mscorlib.dll, netstandard.dll
Define um provedor para notificação baseada em push.
C #Copiar
public interface IObservable<out T>
Parâmetros de tipo
T
O objeto que fornece informações de notificação.
Derivado
Página 44 de Padrao Observer
Derivado
Sistema. Diagnóstico Ouvinte de diagnóstico
Sistema. Rede. Hospedagem. Monitor de memória Asp Net
Sistema. Rede. Hospedagem. Monitor de memória Asp Net
Exemplos
O exemplo a seguir ilustra o padrão de design do observador. Define
uma Locationclasse que contém informações sobre latitude e longitude.
C #Copiar
public struct Location
{
double lat, lon;
public Location(double latitude, double longitude)
{
this.lat = latitude;
this.lon = longitude;
}
public double Latitude
{ get { return this.lat; } }
public double Longitude
{ get { return this.lon; } }
}
A LocationTrackerclasse fornece a implementação IObservable
<T> . Seu TrackLocationmétodo é passado um Locationobjeto anulável que
contém os dados de latitude e longitude. Se o Locationvalor não for null,
o TrackLocationmétodo chama o método OnNext de cada observador.
C #Copiar
public class LocationTracker : IObservable<Location>
{
public LocationTracker()
{
observers = new List<IObserver<Location>>();
}
private List<IObserver<Location>> observers;
public IDisposable Subscribe(IObserver<Location> observer)
{
if (! observers.Contains(observer))
observers.Add(observer);
return new Unsubscriber(observers, observer);
}
private class Unsubscriber : IDisposable
{
private List<IObserver<Location>>_observers;
private IObserver<Location> _observer;
public Unsubscriber(List<IObserver<Location>> observers,
IObserver<Location> observer)
{
this._observers = observers;
this._observer = observer;
}
public void Dispose()
{
if (_observer != null && _observers.Contains(_observer))
Página 45 de Padrao Observer
if (_observer != null && _observers.Contains(_observer))
_observers.Remove(_observer);
}
}
public void TrackLocation(Nullable<Location> loc)
{
foreach (var observer in observers) {
if (! loc.HasValue)
observer.OnError(new LocationUnknownException());
else
observer.OnNext(loc.Value);
}
}
public void EndTransmission()
{
foreach (var observer in observers.ToArray())
if (observers.Contains(observer))
observer.OnCompleted();
observers.Clear();}
}
Se o Locationvalor for null, o TrackLocationmétodo instancia
um LocationUnknownExceptionobjeto, mostrado no exemplo a seguir. Em
seguida, ele chama o método OnError de cada observador e passa
o LocationUnknownExceptionobjeto para ele. Observe
que LocationUnknownExceptionderiva de exceção , mas não adiciona novos
membros.
C #Copiar
public class LocationUnknownException : Exception
{
internal LocationUnknownException()
{ }
}
Observadores registram para receber notificações de um TrackLocationobjeto
chamando seu método IObservable <T> .Subscribe , que atribui uma referência ao
objeto observador a um objeto List <T> genérico particular . O método retorna
um Unsubscriberobjeto, que é uma implementação descartável que permite que os
observadores parem de receber notificações. A LocationTrackerclasse também
inclui um EndTransmissionmétodo. Quando não há mais dados de localização
disponíveis, o método chama o método OnCompleted de cada observador e, em
seguida, limpa a lista interna de observadores.
Neste exemplo, a LocationReporterclasse fornece a implementação IObserver
<T> . Ele exibe informações sobre o local atual no console. Seu construtor inclui
um nameparâmetro, que permite que a LocationReporterinstância se identifique
em sua saída de cadeia. Ele também inclui um Subscribemétodo, que envolve uma
chamada para o método de inscrição do provedor . Isso permite que o método
atribua a referência IDisposable retornada a uma variável
privada. A LocationReporterclasse também inclui um Unsubscribemétodo, que
chama o método IDisposable.Dispose do objeto retornado pelo método IObservable
<T> .Subscribe . O código a seguir define a LocationReporterclasse.
C #Copiar
using System;
public class LocationReporter : IObserver<Location>
{
private IDisposable unsubscriber;
Página 46 de Padrao Observer
private IDisposable unsubscriber;
private string instName;
public LocationReporter(string name)
{
this.instName = name;
}
public string Name
{ get{ return this.instName; } }
public virtual void Subscribe(IObservable<Location> provider)
{
if (provider != null)
unsubscriber = provider.Subscribe(this);
}
public virtual void OnCompleted()
{
Console.WriteLine("The Location Tracker has completed transmitting
data to {0}.", this.Name);
this.Unsubscribe();
}
public virtual void OnError(Exception e)
{
Console.WriteLine("{0}: The location cannot be determined.",
this.Name);
}
public virtual void OnNext(Location value)
{
Console.WriteLine("{2}: The current location is {0}, {1}",
value.Latitude, value.Longitude, this.Name);
}
public virtual void Unsubscribe()
{
unsubscriber.Dispose();
}
}
O código a seguir instancia o provedor e o observador.
C #Copiar
using System;
class Program
{
static void Main(string[] args)
{
// Define a provider and two observers.
LocationTracker provider = new LocationTracker();
LocationReporter reporter1 = new LocationReporter("FixedGPS");
reporter1.Subscribe(provider);
LocationReporter reporter2 = new LocationReporter("MobileGPS");
reporter2.Subscribe(provider);
provider.TrackLocation(new Location(47.6456, -122.1312));
reporter1.Unsubscribe();
provider.TrackLocation(new Location(47.6677, -122.1199));
provider.TrackLocation(null);
provider.EndTransmission();
}
}
// The example displays output similar to the following:
Página 47 de Padrao Observer
// The example displays output similar to the following:
// FixedGPS: The current location is 47.6456, -122.1312
// MobileGPS: The current location is 47.6456, -122.1312
// MobileGPS: The current location is 47.6677, -122.1199
// MobileGPS: The location cannot be determined.
// The Location Tracker has completed transmitting data to MobileGPS.
Observações
As interfaces IObserver <T> e IObservable <T> fornecem um mecanismo
generalizado para notificação baseada em push, também conhecido como padrão
de design do observador. A interface IObservable <T> representa a classe que
envia notificações (o provedor); a interface IObserver <T> representa a classe que
os recebe (o observador). Trepresenta a classe que fornece as informações de
notificação. Em algumas notificações baseadas em push, a implementação
IObserver <T> e Tpode representar o mesmo tipo.
O provedor deve implementar um único método, Subscribe , que indica que um
observador deseja receber notificações baseadas em push. Os chamadores para o
método passam uma instância do observador. O método retorna
uma implementação IDisposable que permite que os observadores cancelem as
notificações a qualquer momento antes que o provedor pare de enviá-las.
A qualquer momento, um determinado provedor pode ter zero, um ou vários
observadores. O provedor é responsável por armazenar referências aos
observadores e garantir que elas sejam válidas antes de enviar
notificações. A interface IObservable <T> não faz nenhuma suposição sobre o
número de observadores ou a ordem na qual as notificações são enviadas.
Os dados atuais. O provedor pode chamar o método IObserver
<T> .OnNext para passar ao observador um Tobjeto que tenha dados atuais,
dados alterados ou dados novos.
•
Uma condição de erro. O provedor pode chamar o método IObserver
<T> .OnError para notificar o observador que alguma condição de erro ocorreu.
•
Não há mais dados. O provedor pode chamar o método IObserver
<T> .OnCompleted para notificar o observador que ele terminou o envio de
notificações.
•
O provedor envia os seguintes três tipos de notificações para o observador
chamando métodos IObserver <T> :
Métodos
Inscreva-se (IObserver
<T>)
Notifica o provedor que um observador deve receber
notificações.
Aplica-se a
.NET Core
2.1 Visualização 2 , 2.0 , 1.1 , 1.0
.NET Framework
4.7.2 , 4.7.1 , 4.7 , 4.6.2 , 4.6.1 , 4.6 , 4.5.2 , 4.5.1 , 4.5
Padrão .NET
2,0 , 1,6 , 1,5 , 1,4 , 1,3 , 1,2 , 1,1 , 1,0
Página 48 de Padrao Observer
Xamarin.Android
7,1
Xamarin.iOS
10,8
Xamarin.Mac
3,0
IObserver <T>•
Veja também
De <https://docs.microsoft.com/pt-br/dotnet/api/system.iobservable-1?view=netframework-4.7.2>
Página 49 de Padrao Observer
Implementando um notificador de eventos
01/06/2015ROBSON CASTILHO
Olá, pessoal
No artigo de hoje, veremos na prática a implementação de um notificador
de eventos (o famoso padrão “Observer”) fazendo uso de um container de
DI. A solução proposta é flexível, utilizando boas práticas de design e
arquitetura de software.
____________________________
Nota: este é um artigo avançado, pois utiliza-se de padrões como
Observer e Ambient Context, conceitos como Dependency Injection,
CompositionRoot e Princípio da Inversão de Dependência e práticas de
teste como mocks. Portanto, esses conceitos são necessários para um
melhor entendimento do “porquê” das coisas. O artigo fornece diversas
referências para todos eles.
____________________________
Vamos lá!
NOTIFICANDO EVENTOS
Um notificador de eventos é uma variação do conhecido design
pattern Publish/Subscribe (ou Observer). Seguindo a definição do
padrão à risca, o “publisher” (nosso Notificador) notifica todos os
interessados (os “observers”) sempre que aquele muda seu estado.
É uma boa solução quando temos uma relação de um para muitos entre
objetos e não queremos acoplá-los por se tratarem de objetos de outras
camadas e para deixar o notificador mais flexível em relação aos seus
“observers”.
Obs. 1: No nosso caso, o notificador não notificará mudança de estado –
veremosque ele nem sequer possui estado – e sim um evento ocorrido em nossa
aplicação.
Obs. 2: O evento, no caso deste artigo, será um evento de domínio, ou
seja, uma situação ocorrida em nossa lógica de negócio e que precisa ser
comunicada a outros contextos (que podem ser outras aplicações). No
entanto, a mesma ideia mostrada no artigo pode ser utilizada para notificar
outros tipos de eventos (de outras camadas).
IMPLEMENTANDO UM NOTIFICADOR BASEADO EM UM CONTAINER
DE DI
Vendo a definição formal do padrão Observer no link mais acima, vocês
podem notar que o “publisher/subject” expõe métodos para adicionar e
remover os “observers” que deverão ser notificados. Em outras palavras, o
notificador mantém a lista de “observers”.
No exemplo deste artigo, a implementação adotada faz uso de um
container de Dependency Injection para localizar seus “observers” e poder
notificá-los, dispensando assim que o notificador conheça a lista de seus
“observers”.
Vejamos como fica o código:
1
2
// o "publisher/subject"
public class NotificadorDeEventosDeDominio
terça-feira, 24 de abril de 2018 10:22
Página 50 de Padrao Observer
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class NotificadorDeEventosDeDominio
{
public static void NotificarSobre<T>(T eventoDeDominio) where T :
IEventoDeDominio
{
foreach (var enderecador in
this.container.GetAllInstances<IEnderecadorDeEvento<T>>())
enderecador.Enderecar(eventoDeDominio);
}
}
// interface de marcação para os eventos de domínio
public interface IEventoDeDominio { }
// o "subscriber/observer"
public interface IEnderecadorDeEvento<T> where T : IEventoDeDominio
{
void Enderecar(T eventoDeDominio);
}
Algumas observações sobre o código:
O notificador não conhece seus “observers”. Ao invés disso, ele pede ao
container de DI para localizá-los, ou seja, estamos usando o container
como um Service Locator. Muita cautela com esse tipo de prática. Não
espalhe o container por toda sua aplicação, pois além da dependência
concreta em relação ao container, você estará escondendo todas as
dependências que suas classes possuem.
•
O container de DI é injetado via construtor e armazenado em um field
usado no método acima (this.container).
•
Qualquer container de DI de sua preferência pode ser utilizado.•
IEventoDeDominio é apenas uma interface de marcação para
restringirmos o parâmetro do método e os “observers” para aceitarem
somente objetos que sejam eventos de domínio.
•
Tanto o notificador como as interfaces usadas estão na camada de
domínio, pois estão diretamente relacionados.
•
Um exemplo de uso seria o seguinte:
1
2
3
4
5
6
7
8
9
10
11
public class Despesa
{
public void Baixar()
{
// baixa da despesa ....
// notificando evento
NotificadorDeEventosDeDominio
.NotificarSobre(new DespesaPaga(idDespesa, valorPago));
}
}
No exemplo acima, uma entidade do domínio, após realizar sua operação
de baixa, notifica aos interessados a ocorrência do evento DespesaPaga.
Um “observer” que receberia essa notificação seria qualquer objeto que
implemente IEnderecadorDeDominio<DespesaPaga>.
ALGUNS PROBLEMAS
A solução mostrada apresenta alguns problemas:
1. Como dito antes, o NotificadorDeEventosDeDominio encontra-se na
camada de domínio, juntamente com a classe Despesa e demais classes
que modelam o negócio. Como usamos o container de DI na
implementação do notificador, somos obrigados a manter uma referência
do container no domínio, ou seja, o domínio conhece um detalhe técnico (o
Página 51 de Padrao Observer
do container no domínio, ou seja, o domínio conhece um detalhe técnico (o
container).
De um ponto de vista arquitetural, devemos deixar o domínio o mais
ignorante possível de detalhes técnicos (mais aqui).
Portanto, precisamos remover essa dependência do domínio para o
container de DI!
2. Testabilidade! Conseguimos garantir que o estado do objeto Despesa foi
alterado na baixa mas não conseguimos testar que a notificação realmente
foi chamada, uma vez que fazemos isso por meio de um
método estático do notificador.
VERSÃO MELHORADA
Para sanar os problemas acima, precisamos abstrair o notificador de
eventos, aplicando o Princípio da Inversão de Dependência.
No entanto, quero continuar usando o notificador como um ambient
context, ou seja, não quero poluir a interface das minhas entidades de
domínio injetando o notificador via construtor, método ou propriedade.
Vejamos como fica a nova versão, começando pela abstração:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public abstract class NotificadorDeEventosDeDominio
{
private static NotificadorDeEventosDeDominio _notificadorCorrente;
public static NotificadorDeEventosDeDominio NotificadorCorrente
{
get { return _notificadorCorrente; }
set
{
if (value == null)
throw new ArgumentNullException("value");
_notificadorCorrente = value;
}
}
public abstract void NotificarSobre<T>(T eventoDeDominio) where T :
IEventoDeDominio;
}
O notificador concreto é praticamente o anterior, porém herdando da
abstração acima:
1
2
3
4
5
6
7
8
9
10
public class NotificadorBaseadoEmContainerDeDi :
NotificadorDeEventosDeDominio
{
// construtor com injecao do container omitido
public override void NotificarSobre<T>(T eventoDeDominio)
{
foreach (var enderecador in
this.container.GetAllInstances<IEnderecadorDeEvento<T>>())
enderecador.Enderecar(eventoDeDominio);
}
}
Agora, os objetos de domínio passam a depender da abstração e não
mais do notificador concreto:
1
2
3
4
5
6
public class Despesa
{
public void Baixar()
{
// baixa da despesa ...
Página 52 de Padrao Observer
6
7
8
9
10
11
// notificando evento
NotificadorDeEventosDeDominio
.NotificadorCorrente.NotificarSobre(new
DespesaPaga(idDespesa, valorPago));
}
}
Sendo assim, podemos mover o notificador concreto do domínio para o
assembly que faz uso do container de DI para subir a aplicação. Com isso,
o domínio passa a não conhecer mais o container de DI.
GARANTINDO A TESTABILIDADE
Com a nova versão, podemos testar melhor os objetos de domínio que
fazem uso do notificador.
Vejamos como fica o teste da entidade Despesa, que verifica se o
notificador foi realmente chamado (e com o evento correto):
1
2
3
4
5
6
7
8
9
10
11
12
13
[Test]
public void Deve_notificar_sobre_baixa_da_despesa()
{
var notificadorDeEventosDeDominioMock = new
Mock<NotificadorDeEventosDeDominio>();
NotificadorDeEventosDeDominio.NotificadorCorrente =
notificadorDeEventosDeDominioMock.Object;
var despesa = new Despesa();
despesa.Baixar();
var despesaPaga = (new DespesaPaga(100,
100.50)).AsSource().OfLikeness<DespesaPaga>().CreateProxy();
notificadorDeEventosDeDominioMock.Verify(mock =>
mock.NotificarSobre(despesaPaga), Times.Once);
}
Vejam que, para fins de teste, configuramos o NotificadorCorrente com um
mock ao invés de usarmos o notificador concreto e fazemos a asserção do
teste verificando que o método NotificarSobre() foi realmente chamado
com o evento correto e somente uma vez.
(Obs.: Neste teste, utilizei a lib SemanticComparison para criar o objeto
esperado como parâmetro do método. Para saber mais sobre ela, leia meu
post anterior sobre comparação semântica.)
USANDO O NOTIFICADOR CONCRETO
Ok, usamos um mock como NotificadorCorrente nos testes de unidade. E
no código de produção? Onde usamos o notificador concreto?
A resposta é simples: configuramos o NotificadorCorrente com o
notificador concreto no start da aplicação (*)e nunca mais mexemos com
ele:
1
2
3
var container = ConfigurarContainer();
NotificadorDeEventosDeDominio.NotificadorCorrente =
new NotificadorBaseadoEmContainerDeDi(container);
(*) Esse local é chamado de CompositionRoot e falei sobre ele na
minha série sobre Dependency Injection.
Relembrando que o notificador concreto fica junto do container no mesmo
assembly, que pode ser o assembly referente à camada de apresentação
ou um específico para o CompositionRoot (o que eu acho mais adequado
em projetos reais).
E OS OBSERVERS CONCRETOS?
Os observers concretos – aqueles que implementam
Página 53 de Padrao Observer
Os observers concretos – aqueles que implementam
IEnderecadorDeEvento<T> – ficam na camada correspondente ao seu propósito.
Se o observer, por exemplo, informa alguma coisa para a view ele ficará na
camada de apresentação. Outro observer que trata o evento comunicando-se
com algum serviço externo (e-mail, API REST, messaging) certamente ficará na
camada de infra-estrutura. E assim por diante.
CONCLUINDO
Vimos, passo-a-passo, como criar um notificador de eventos (padrão
Observer) usando um container de DI em sua implementação.
A solução mostrada apresenta baixo acoplamento entre os módulos, o que
garante testabilidade dos clientes do notificador e atende a regra de
dependências entre camadas (sempre da mais externa para a mais
interna).
Foi um artigo avançado, que exigiu conhecimento de diversos padrões de
design e arquitetura de software, mas espero ter conseguido passar a
ideia.
Para reforçar o entendimento, disponibilizei no meu github o código do
artigojuntamente com um pequeno exemplo funcional de uma app console,
que faz uso de tudo que foi visto aqui.
Espero que tenham gostado. Comentários, dúvidas, críticas? Só escrever!
[]s e até a próxima!
Avalie isto:
3 Votos
Compartilhe:
24Compartilhar no Facebook(abre em nova janela)24•
Clique para compartilhar no Twitter(abre em nova janela)•
Clique para compartilhar no LinkedIn(abre em nova janela)•
Clique para imprimir(abre em nova janela)•
Conhecendo Design Patterns e o padrão StrategyEm "patterns"
Sobre camadas, cebolas e hexágonos...Em "Arquitetura de Software"
Como foi o workshop com Ralph Johnson do GoFEm "carreira"
Categorias: .net, Arquitetura de Software Tags: arquitetura de software, design
patterns, DI containers, domain events, observer
Navegação de Posts
← Comparando objetos semanticamente
FluentBuilder – Meu primeiro pacote Nuget→
4 comentários em “Implementando um
notificador de eventos”
Página 54 de Padrao Observer
Yan Justino disse:
01/06/2015 ÀS 20:38
Parabéns pelo post, Robson! Eu gosto muito do Domain Events. Geralmente
eu coloco a implemntação dele num Shared Kernel e Interfaceio o container DI.
Vou experimentar essa abordagem no próximo projeto.
RESPONDER
Robson Castilho disse:
01/06/2015 ÀS 20:46
Olá, Yan
Obrigado.
Então, já fiz isso mas eliminei essa interface pelo seguinte:
Não acho que faça sentido uma interface pra um container de DI (dado que vc
so utilizará um e normalmente em um só local da app – o CompositionRoot).
Além disso, voce teria que coloca-la no dominio (que faz ainda menos sentido pra
mim) ou numa camada acima (e aí o dominio teria que fazer referencia a alguem mais
acima, mesmo problema de depender do container, embora não seja o concreto).
Por esses motivos, matei a interface e uso o container concreto (apenas no
CompositionRoot, em duas ou tres classes).
IMHO!
[]s
(AH..deletei seu outro comentário que era praticamente uma cópia desse :)).
RESPONDER
James Diniz disse:
04/06/2015 ÀS 20:03
Mais um excelente post, parabéns Robson.
RESPONDER
Robson Castilho disse:
04/06/2015 ÀS 20:08
Obrigado, James
De <https://robsoncastilho.com.br/2015/06/01/implementando-um-notificador-de-eventos/>
Página 55 de Padrao Observer