CodeGym /Cursos /C# SELF /Padrão "Observador" ( Obser...

Padrão "Observador" ( Observer)

C# SELF
Nível 53 , Lição 3
Disponível

1. Introdução

Imagine que você tem um objeto que faz alguma coisa — por exemplo, um botão ou o nosso Worker. Ao mesmo tempo existem vários outros objetos que precisam reagir a essas ações. Se você codificar todos os possíveis "listeners" diretamente dentro da classe Worker, manter esse código vira um pesadelo: qualquer alteração na lista de assinantes vai exigir mudanças dentro do próprio Worker.

Isso viola o princípio aberto-fechado (OCP) e é considerado uma má prática arquitetural.

Padrão Observer: ideia geral

O padrão "Observador" (Observer) resolve esse problema. Ele permite que um objeto-publicador notifique qualquer número de objetos-interessados (listeners) sobre mudanças ocorridas, sem saber quem são esses listeners nem o que eles fazem. O publicador simplesmente "lança o evento" e quem quiser reage do jeito que achar melhor.

Analogia: assinatura de uma newsletter. A redação ou canal (publicador) envia uma nova mensagem, e todos os assinantes (observadores) recebem. A redação não sabe quem são todas essas pessoas — nem precisa saber.

Curiosidade: o "Observador" é tão popular que faz parte oficialmente do conjunto dos "Gang of Four" (GoF).

Observer em C#: concretização via eventos e delegates

Em C# o padrão "Observador" vem "na caixa" através dos mecanismos de eventos e delegates. Evento é um "ponto de extensão" onde handlers diferentes podem se inscrever. Em vez de manter manualmente uma lista de assinantes, o mecanismo de eventos da linguagem faz isso por você. Abaixo vemos uma implementação "manual" e, depois, a versão usando eventos.

2. Implementação clássica do Observer sem usar eventos

Vamos ver como isso poderia ficar se a linguagem não tivesse eventos:


// Interface do observador
public interface IObserver
{
    void Update(string message);
}

// Publicador
public class Worker
{
    private List<IObserver> observers = new List<IObserver>();

    public void Subscribe(IObserver observer)
    {
        observers.Add(observer);
    }

    public void Unsubscribe(IObserver observer)
    {
        observers.Remove(observer);
    }

    public void DoWork()
    {
        Console.WriteLine("Worker está trabalhando...");
        NotifyObservers("Trabalho concluído!");
    }

    private void NotifyObservers(string message)
    {
        foreach (var observer in observers)
        {
            observer.Update(message);
        }
    }
}

// Observador concreto
public class WorkListener : IObserver
{
    public void Update(string message)
    {
        Console.WriteLine($"\nWorkListener recebeu a mensagem: {message}");
    }
}

Inicialização:


var worker = new Worker();
var listener = new WorkListener();
worker.Subscribe(listener);
worker.DoWork();

Dica: aqui a lista de assinantes (List<IObserver> observers) é mantida manualmente, e subscribe/unsubscribe são métodos explícitos Subscribe/Unsubscribe.

3. Eventos e delegates — implementação "high-level" do Observer

Podemos fazer a mesma coisa de forma mais simples e elegante usando eventos. Isso é o Observer no estilo C#:


public class Worker
{
    public event EventHandler<WorkCompletedEventArgs>? WorkCompleted;

    public void DoWork()
    {
        Console.WriteLine("Worker está trabalhando...");
        OnWorkCompleted("Trabalho concluído!");
    }

    protected virtual void OnWorkCompleted(string message)
    {
        WorkCompleted?.Invoke(this, new WorkCompletedEventArgs { Message = message });
    }
}

public class WorkCompletedEventArgs : EventArgs
{
    public string Message { get; set; }
}

public class WorkListener
{
    public void OnWorkCompleted(object? sender, WorkCompletedEventArgs e)
    {
        Console.WriteLine($"WorkListener recebeu a mensagem: {e.Message}");
    }
}

// Assinatura:
var worker = new Worker();
var listener = new WorkListener();
worker.WorkCompleted += listener.OnWorkCompleted;
worker.DoWork();

Vantagens dessa abordagem:

  • Não precisa manter manualmente uma lista de assinantes.
  • Disponíveis todas as facilidades de eventos: múltiplas assinaturas, unsubscribe, lambdas.
  • Segurança garantida: só o publicador pode invocar o evento.
  • Baixo acoplamento: o publicador não sabe nada sobre os listeners.

4. Como o "observador" se encaixa no nosso app

Vamos integrar o padrão Observer no nosso app de console. Deixe o Worker ter quantos handlers quiser, que vão reagir ao término do trabalho de formas diferentes: alguém escreve no console, alguém conta quantos trabalhos foram feitos, alguém envia um email "Chefe! Tudo pronto!".

Expandindo o código com exemplos


// Segundo listener-contador
public class WorkCounter
{
    public int Count { get; private set; }

    public void OnWorkCompleted(object? sender, WorkCompletedEventArgs e)
    {
        Count++;
        Console.WriteLine($"Trabalho registrado. Total: {Count} concluído(s).");
    }
}

// Criamos os objetos
var worker = new Worker();
var listener = new WorkListener();
var counter = new WorkCounter();

// Duas assinaturas
worker.WorkCompleted += listener.OnWorkCompleted;
worker.WorkCompleted += counter.OnWorkCompleted;

// Simulação de vários trabalhos
worker.DoWork();
worker.DoWork();
// Output:
// Worker está trabalhando...
// WorkListener recebeu a mensagem: Trabalho concluído!
// Trabalho registrado. Total: 1 concluído(s).
// Worker está trabalhando...
// WorkListener recebeu a mensagem: Trabalho concluído!
// Trabalho registrado. Total: 2 concluído(s).

Assim, você adiciona observadores à medida que precisa, sem mexer em nenhuma linha do código do Worker. A classe Worker permanece inalterada, enquanto o comportamento do sistema é estendido via assinantes.

5. Nuances úteis

Exemplo real: Observer em interfaces e GUI

O padrão Observer é a base de todos os frameworks de GUI. Em Windows Forms ou WPF o clique de um botão dispara o evento Click. Você escreve handlers (observadores) que reagem a esse evento — e nem a sua classe Button, nem a biblioteca .NET precisam saber quem são os seus assinantes.


// Em WPF ou WinForms (mais ou menos)
myButton.Click += (s, e) => MessageBox.Show("O usuário clicou no botão!");

Observer em projetos reais

  • Interface do usuário (reações a cliques, mudanças, timers, etc.).
  • Sistemas de notificações e eventos.
  • Plugins para sistemas extensíveis (o core gera eventos, extensões se inscrevem).
  • Sistemas distribuídos e engines de jogo (cadeias reativas fracamente acopladas).

Resumindo: se você precisa de um sistema extensível onde partes podem reagir a mudanças em outras — Observer must have!

7. Características, erros comuns e como evitá-los

Dificuldades potenciais ao usar Observer

Vazamentos de memória. Se um assinante se inscreveu num evento mas não se desinscreveu (especialmente em objetos de longa duração), o garbage collector não vai conseguir liberar esse objeto, porque o publicador ainda mantém uma referência a ele via delegate do evento. Isso é crítico quando o assinante não é mais necessário, mas o publicador continua vivo.

Assinatura múltipla. Se o mesmo handler for inscrito duas vezes, ele será chamado duas vezes — o que pode causar duplicação de ações e efeitos indesejados.

Exceções em handlers. Se um dos handlers lançar uma exceção, a execução dos assinantes seguintes pode ser interrompida. Planeje handlers resilientes e, se preciso, invoque-os manualmente dentro de um try-catch para garantir que os demais assinantes sejam executados mesmo se um falhar.

Esquema comum de vazamentos

flowchart LR
    Publisher["Publicador
(Worker)"] -- evento --> ObserverA["Ouvinte A (Vivo!)"] Publisher -- evento --> ObserverB["Ouvinte B (Vazamento: esqueceu de desinscrever)"]
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION