Define uma relação de um-para-muitos entre objetos em que se o objeto principal (Subject) mudar seu estado as suas dependências (Observers) são notificadas e atualizadas automaticamente.

O pattern Observer define como estabelecer a relação entre os objetos. As chaves deste pattern são o Subject e os Observers. O Subject deve ter uma ou várias dependências de Observers. Todos os Observers são notificados sempre que o Subject mudar seu estado.

O Subject é um publicador de notificações, ele envia as notificações para todos os objetos que o Assinam.

Use o padrão Observer em qualquer uma das seguintes situações:

  • Quando uma abstração possui dois aspectos e um depende do outro. Encapsular esses aspectos em objetos separados permite a variação e a reutilização de forma independentes;
  • Quando a mudança em um objeto requer a troca de estado em outros e não se sabe quantos objetos precisam ser alterados;
  • Quando um objeto deve ser capaz de notificar outros objetos sem saber quem eles são. Em outras palavras, os objetos não podem ser fortemente acoplados.

A estrutura do Observer é representada no seguinte diagrama:

Resultado de imagem para design pattern observer
Imagem do livro Design Patterns: Elements of Reusable Object-Oriented Software pag. 294

Subject (ISubject)

É a interface que fornece os métodos de incluir, deletar e notificar os Observers.

Observer (IObserver)

Esta interface tem apenas um método (Update). Todos os objetos que querem ser notificados devem implementar esta interface.

ConcreateSubject (Subject)

É a implementação do ISubject. Aqui vamos criar as implementações para incluir, deletar e notificar os objetos Observers.

ConcreateObserver (Observer)

Qualquer objeto pode implementar o IObserver. Ao fazer esta implementação, toda vez que o Subject for alterado os objetos que o implementam serão notificados e alterados.

Vamos ao código de exemplo:

Neste exemplo temos uma classe Cotação Atual, ela será nosso Subject e será responsável por enviar as alterações para todos os Observers.

public class CurrencyQuote : ISubject
    {
        private readonly List<IObserver> _observers;
        private decimal _dollar;
        private decimal _euro;
        private decimal _pound;

        public CurrencyQuote()
        {
            _observers = new List<IObserver>();
        }

        public void RegisterObserver(IObserver observer)
        {
            _observers.Add(observer);
        }

        public void RemoveObserver(IObserver observer)
        {
            var i = _observers.IndexOf(observer);

            if (i > -1)
            {
                _observers.RemoveAt(i);
            }
        }

        public void NotifyObservers()
        {
            foreach (var item in _observers)
            {
                item.Update(_dollar, _euro, _pound);
            }
        }

        public void Changed()
        {
            NotifyObservers();
        }

        public void SetValues(decimal dollar, decimal euro, decimal pound)
        {
            _dollar = dollar;
            _euro = euro;
            _pound = pound;

            Changed();
        }
    }

Como podemos ver, esta classe é responsável por orquestrar os estados das propriedades Dolar, Euro e Libra. Além de registrar os Observers e e notificar qualquer mudança que um de seus estados sofra.

Agora, vamos criar 3 Observers que assinarão este Subject. Cada Observer tratará a notificação de forma particular. Lembre-se, o que o Observer faz é implícito para o Subject. Ele apenas notifica e seus Observers fazem o que precisam fazer.

Trader 1

O Observer Trader1 recebe a notificação de mudança e exibe todas as cotações na tela:

public class Trader1 : IObserver
    {
        private decimal _dollar;
        private decimal _euro;
        private decimal _pound;

        public Trader1(ISubject subject)
        {
            subject.RegisterObserver(this);
        }

        public void Update(decimal dollar, decimal euro, decimal pound)
        {
            _dollar = dollar;
            _euro = euro;
            _pound = pound;

            Display();
        }

        public void Display()
        {
            Console.WriteLine($"Cotação atual Dolar: R${_dollar}, Euro R${_euro} e Libra: R${_pound}");
        }
    }

Trader 2

O Observer Trader2 recebe a notificação de mudança, mas ele não trabalha com a moeda Libra, então, simplesmente a ignora:

public class Trader2 : IObserver
    {
        private decimal _dollar;
        private decimal _euro;

        public Trader2(ISubject subject)
        {
            subject.RegisterObserver(this);
        }

        public void Update(decimal dollar, decimal euro, decimal pound)
        {
            _dollar = dollar;
            _euro = euro;

            Display();
        }

        public void Display()
        {
            Console.WriteLine($"Cotação atual Dolar: R${_dollar} e Euro R${_euro}");
        }
    }

Envia email da cotação

O Observer SendQuoteEmail recebe a notificação de mudança e possui outro comportamento. Ele não exibe na tela, ele envia por e-mail para os investidores toda vez que a cotação muda.

public class SendQuoteEmail : IObserver
    {
        private decimal _dollar;
        private decimal _euro;
        private decimal _pound;

        public SendQuoteEmail(ISubject subject)
        {
            subject.RegisterObserver(this);
        }

        public void Update(decimal dollar, decimal euro, decimal pound)
        {
            _dollar = dollar;
            _euro = euro;
            _pound = pound;

            SendEmail();
        }

        public void SendEmail()
        {
            Console.WriteLine($"Este observer envia as informações por e-mail");

            // SMTP IMPLEMENTATION
        }
    }

Na classe Program, vamos criar o Subject, assinar os Observers e alterar o estado do Subject. Veja que basta alterar o estado do Subject que todos os Observers são comunicados e fazem suas respectivas tarefas.

class Program
    {
        static void Main(string[] args)
        {
            var quote = new CurrencyQuote();
            var trader1 = new Trader1(quote);
            var trader2 = new Trader2(quote);
            var sendQuoteEmail = new SendQuoteEmail(quote);

            quote.SetValues(3.74M, 4.26M, 4.74M);
            Console.WriteLine("====================================");
            quote.SetValues(3.61M, 4.17M, 4.65M);
            Console.WriteLine("====================================");
            quote.SetValues(3.58M, 4.09M, 4.44M);

            Console.ReadKey();
        }
    }

Este código está disponível no meu GitHub.