Resumo : neste tutorial, você aprenderá como usar o padrão observador C# para notificar objetos (observadores) quando o estado de outro objeto (sujeito) muda.
Introdução ao padrão C# Observer
O padrão Observer define uma dependência um-para-muitos entre objetos para que quando um objeto (conhecido como sujeito) muda de estado, todas as suas dependências conhecidas como observadores sejam notificadas e atualizadas automaticamente.
O diagrama UML a seguir ilustra o padrão Observer:
Aqui estão os participantes do padrão observador:
ISubject
fornece uma interface para assinatura e cancelamento de assinaturaIObserver
de objetos.IObserver
define uma interface de atualização para objetos que devem ser notificados sobre alterações em um assunto.
armazena o estado de interesse dosConcreteSubject
ConcreteObserver
objetos. O
envia uma notificação aos seus observadores quando seu estado muda.ConcreteSubject
ConcreteObserver
tem uma referência a umConcreteSubject
objeto. Ele também implementa aIObserver
interface para manter seu estado consistente com o estado do assunto.
Os objetos ISubject
e IObservers
são vinculados por meio de um relacionamento um-para-muitos. Isso significa que um assunto pode ter vários observadores inscritos nele.
Quando o estado muda ISubject
, ele notifica todos os seus IObserver
objetos chamando seus Update
métodos.
Os IObserver
objetos podem então acessar o ISubject
novo estado do e atualizar seu próprio estado.
Exemplo de padrão C# Observer
Digamos que você queira desenvolver um programa que gerencie cotações de ações. Sempre que o preço da ação muda, você deseja exibi-lo no console e também salvar os dados em um arquivo. Para fazer isso, você pode usar o padrão Observer.
Primeiro, defina uma IObserver
interface que possua um método chamado
. O Update
método possui dois parâmetros Update
symbol
er price
representando o estado do estoque:
public interface IObserver
{
public void Update(string symbol, decimal price);
}
Linguagem de código: C# ( cs )
Segundo, defina a ISubject
interface que gerencia os IObserver
objetos, como assinatura, cancelamento de assinatura e notificação dos IObserver
objetos:
public interface ISubject
{
void Subscribe(IObserver observer);
void Unsubscribe(IObserver observer);
void NotifyObservers();
}
Linguagem de código: C# ( cs )
Terceiro, defina a Stock
classe que implementa a ISubject
interface:
public class Stock : ISubject
{
public string Symbol { get; set; }
private decimal _price;
public decimal Price
{
get => _price;
set
{
if (_price != value)
{
_price = value;
NotifyObservers();
}
}
}
public Stock(string symbol, decimal price)
{
Symbol = symbol;
Price = price;
}
private readonly List<IObserver> _observers = new();
public void NotifyObservers() => _observers.ForEach(observer => observer.Update(Symbol, Price));
public void Subscribe(IObserver observer) => _observers.Add(observer);
public void Unsubscribe(IObserver observer) => _observers.Remove(observer);
}
Linguagem de código: C# ( cs )
A Stock
classe possui duas propriedades Symbol
que Price
representam o símbolo da ação e o preço respectivamente.
A Stock
classe mantém uma lista de
objetos como um arquivo IObserver
List<IObserver>
. Como a Stock
classe implementa a ISubject
interface, ela precisa fornecer implementações para os métodos Subscribe
, Unsubscribe
e :NotifyObservers
Subscribe
– adiciona umIObserver
objeto à_observers
lista.Unsubscribe
– remove umIObserver
objeto da_observers
lista.NotifyObservers
– notificaIObserver
o objeto na_observers
lista iterando a lista e chamando oUpdate()
método de cada objeto.
No Price
levantador, se o preço mudar, chamamos o NotifiyObservers
método para notificar os observadores chamando seu Update
método.
Quarto, defina uma Display
classe que exiba o estoque sempre que o preço mudar. A Display
classe implementa a IObserver
interface:
public class Display : IObserver
{
private readonly ISubject _subject;
public Display(ISubject subject)
{
_subject = subject;
_subject.Subscribe(this);
}
public void Update(string symbol, decimal price)
{
Console.WriteLine($"{symbol}: {price}");
}
}
Linguagem de código: C# ( cs )
Observe que o Display
método mantém um membro como um ISubject
objeto. No construtor, atribui o sujeito ao _subject
membro e chama o Subscribe()
método do _subject
objeto para se inscrever como observador.
Quinto, defina uma Logger
classe que implemente a IObserver
interface:
public class Logger : IObserver
{
private readonly ISubject _subject;
private readonly string _filename;
public Logger(ISubject subject, string filename)
{
_subject = subject;
_subject.Subscribe(this);
_filename = filename;
}
public void Update(string symbol, decimal price)
{
using var streamWriter = File.AppendText(_filename);
streamWriter.WriteLine($"{symbol}:{price}");
}
}
Linguagem de código: C# ( cs )
A Logger
classe é semelhante à Display
classe, exceto que salva os dados do estoque em um arquivo de texto especificado pelo nome do arquivo.
Finalmente, defina a classe Program com o Main()
método como ponto de entrada do programa:
public class Program
{
public static void Main(string[] args)
{
// Create a new stock
var stock = new Stock("ABC", 100m);
// Create two observers Display & Logger
var display = new Display(stock);
var logger = new Logger(stock, "stock.txt");
// Change the price, both display and logger
// will be notified and updated
stock.Price += 2;
stock.Price -= 1;
Console.ReadLine();
// remove the logger from the observer list
stock.Unsubscribe(logger);
// Change the price, only the display is notified
// and updated
stock.Price += 3;
Console.ReadLine();
}
}
Linguagem de código: C# ( cs )
No Main()
método, criamos um objeto de estoque como sujeito e os objetos Display
e Logger
como observadores.
Em seguida, alteramos o preço da ação duas vezes, o que atualiza os objetos Display
e Logger
. Como resultado, você verá o console exibir o estoque com novos preços:
ABC: 102
ABC: 101
Press Enter (or Return) to remove the Logger from the observer list
Linguagem de código: C# ( cs )
e o stock.txt
arquivo terá duas entradas.
Se você pressionar a tecla Enter (ou Return), a ação remove o Logger
objeto de sua lista de observadores e altera seu preço. Neste ponto, apenas o Display
objeto é notificado e atualizado:
ABC: 102
ABC: 101
Press Enter (or Return) to remove the Logger from the observer list
ABC: 104
Linguagem de código: C# ( cs )
Como Logger
não está na lista de observadores, não é notificado e atualizado. Portanto, o stock.txt
não tem novas entradas.
Resumo
- Use o padrão C# Observer para definir uma dependência um-para-muitos entre objetos, de modo que quando um objeto for alterado, todos os seus dependentes sejam notificados e atualizados automaticamente.