Padrão de observador C#

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:

Padrão de observador C#

Aqui estão os participantes do padrão observador:

  • ISubjectfornece uma interface para assinatura e cancelamento de assinatura IObserverde objetos.
  • IObserverdefine uma interface de atualização para objetos que devem ser notificados sobre alterações em um assunto.
  • ConcreteSubjectarmazena o estado de interesse dos ConcreteObserverobjetos. O ConcreteSubjectenvia uma notificação aos seus observadores quando seu estado muda.
  • ConcreteObservertem uma referência a um ConcreteSubjectobjeto. Ele também implementa a IObserverinterface para manter seu estado consistente com o estado do assunto.

Os objetos ISubjecte IObserverssã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 IObserverobjetos chamando seus Updatemétodos.

Os IObserverobjetos podem então acessar o ISubjectnovo 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 IObserverinterface que possua um método chamado Update. O Updatemétodo possui dois parâmetros symboler pricerepresentando o estado do estoque:

public interface IObserver
{
    public void Update(string symbol, decimal price);
}Linguagem de código:  C#  ( cs )

Segundo, defina a ISubjectinterface que gerencia os IObserverobjetos, como assinatura, cancelamento de assinatura e notificação dos IObserverobjetos:

public interface ISubject
{
    void Subscribe(IObserver observer);
    void Unsubscribe(IObserver observer);
    void NotifyObservers();
}Linguagem de código:  C#  ( cs )

Terceiro, defina a Stockclasse que implementa a ISubjectinterface:

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 Stockclasse possui duas propriedades Symbolque Pricerepresentam o símbolo da ação e o preço respectivamente.

A Stockclasse mantém uma lista de IObserverobjetos como um arquivo List<IObserver>. Como a Stockclasse implementa a ISubjectinterface, ela precisa fornecer implementações para os métodos Subscribe, Unsubscribee :NotifyObservers

  • Subscribe– adiciona um IObserverobjeto à _observerslista.
  • Unsubscribe– remove um IObserverobjeto da _observerslista.
  • NotifyObservers– notifica IObservero objeto na _observerslista iterando a lista e chamando o Update()método de cada objeto.

No Pricelevantador, se o preço mudar, chamamos o NotifiyObserversmétodo para notificar os observadores chamando seu Updatemétodo.

Quarto, defina uma Displayclasse que exiba o estoque sempre que o preço mudar. A Displayclasse implementa a IObserverinterface:

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 Displaymétodo mantém um membro como um ISubjectobjeto. No construtor, atribui o sujeito ao _subjectmembro e chama o Subscribe()método do _subjectobjeto para se inscrever como observador.

Quinto, defina uma Loggerclasse que implemente a IObserverinterface:

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 Loggerclasse é semelhante à Displayclasse, 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 Displaye Loggercomo observadores.

Em seguida, alteramos o preço da ação duas vezes, o que atualiza os objetos Displaye 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 listLinguagem de código:  C#  ( cs )

e o stock.txtarquivo terá duas entradas.

Se você pressionar a tecla Enter (ou Return), a ação remove o Loggerobjeto de sua lista de observadores e altera seu preço. Neste ponto, apenas o Displayobjeto é notificado e atualizado:

ABC: 102
ABC: 101
Press Enter (or Return) to remove the Logger from the observer list

ABC: 104Linguagem de código:  C#  ( cs )

Como Loggernão está na lista de observadores, não é notificado e atualizado. Portanto, o stock.txtnã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.

Deixe um comentário

O seu endereço de email não será publicado. Campos obrigatórios marcados com *