Padrão Observer: Mantendo os Objetos Informados

Vinicius Climaco
7 min readOct 2, 2023

--

Em um mundo digital onde a interatividade e as atualizações em tempo real são reis, o Padrão Observer se destaca como um dos pilares da comunicação eficiente entre objetos. Após nossa jornada pelo Padrão Strategy, é hora de adentrar o universo onde objetos “falam” entre si, mantendo-se atualizados e sincronizados.

Cotidiano

Imagine um cenário onde você possui um aplicativo de notícias. Os usuários se inscrevem para receber atualizações sobre seus tópicos de interesse. Quando uma nova notícia é publicada, os usuários inscritos são notificados instantaneamente. Este é o Padrão Observer em ação.

Definição GoF

Define uma dependência um-para-muitos entre objetos, de modo que quando um objeto muda de estado, todos os seus dependentes são notificados e atualizados automaticamente.

A Mágica do Observer

No coração deste padrão, temos o ‘Subject’ (ou sujeito), que é monitorado por observadores. Quando o estado do sujeito muda, todos os observadores registrados são notificados. É uma dança bem orquestrada de objetos, garantindo que todos estejam na mesma página.

Diagrama UML

Padrão Observer

Observer (Interface Observador)

Contextualização: Representa os assinantes ou usuários que desejam receber atualizações. No contexto de um aplicativo de notícias, são os leitores que se inscreveram para receber notificações sobre novas publicações.

Responsabilidades:

  • Definir o método de atualização que será implementado pelos observadores concretos.
  • Receber e processar as notificações do sujeito.

ConcreteObserver (Observador Concreto)

Contextualização: São as implementações específicas dos observadores. No aplicativo de notícias, podem representar categorias de usuários, como assinantes premium ou usuários gratuitos, cada um recebendo notificações de maneira ligeiramente diferente.

Responsabilidades:

  • Implementar o método de atualização definido pela interface IObserver.
  • Reagir às notificações do sujeito de acordo com a lógica específica de cada tipo de observador.

ISubject (Interface Sujeito)

Contextualização: Representa a entidade que os observadores estão monitorando. No contexto do aplicativo de notícias, é a plataforma ou o sistema que publica as notícias.

Responsabilidades:

  • Registrar e remover observadores.
  • Notificar os observadores quando ocorre uma mudança de estado ou uma atualização.

4. ConcreteSubject (Sujeito Concreto)

Contextualização: É a implementação específica do sujeito que mantém o estado e notifica os observadores sobre as mudanças. No aplicativo de notícias, pode representar um tópico ou categoria específica de notícias.

Responsabilidades:

  • Manter o estado que é monitorado pelos observadores.
  • Notificar os observadores registrados quando o estado muda.

Fluxo de Interação

Contextualização: O diagrama UML ilustra como os observadores são notificados sobre as mudanças no sujeito. No aplicativo de notícias, mostra o fluxo desde a publicação de uma nova notícia até a recepção da notificação pelos assinantes.

Passos:

  1. O sujeito concreto muda de estado (por exemplo, uma nova notícia é publicada).
  2. O sujeito concreto notifica todos os observadores registrados sobre a mudança.
  3. Cada observador concreto, ao ser notificado, reage de acordo com sua lógica específica de implementação (por exemplo, exibindo a notificação para o usuário).

Implementação Prática

Vamos mergulhar em um exemplo concreto, usando C# para ilustrar o Padrão Observer em um aplicativo de notícias.

public interface IObservador
{
void Atualizar(int valor);
}
public class ObservadorA : IObservador
{
private readonly string _nome;

public ObservadorA(string nome)
{
_nome = nome;
}

public void Atualizar(int valor)
{
Console.WriteLine($"{_nome} recebeu a atualização: O valor foi alterado para {valor}");
}
}

public class ObservadorB : IObservador
{
private readonly string _nome;

public ObservadorB(string nome)
{
_nome = nome;
}

public void Atualizar(int valor)
{
Console.WriteLine($"{_nome} foi notificado: O novo valor é {valor}");
}
}
public interface ISujeito
{
void AdicionarObservador(IObservador observador);
void RemoverObservador(IObservador observador);
void NotificarObservadores();
}
public class Sujeito : ISujeito
{
private readonly List<IObservador> _observadores = new List<IObservador>();
private int _valor;

public int Valor
{
get => _valor;
set
{
_valor = value;
NotificarObservadores();
}
}

public void AdicionarObservador(IObservador observador)
{
_observadores.Add(observador);
}

public void RemoverObservador(IObservador observador)
{
_observadores.Remove(observador);
}

public void NotificarObservadores()
{
foreach (var observador in _observadores)
{
observador.Atualizar(_valor);
}
}
}

Acima, temos as classes e interfaces utilizadas em nossa solução, mantive os nomes em português num formato mais didático onde todos terão entendimento de cada passo realizado.
Na classe Program abaixo temos um exemplo de utilização de nossa implementação do Padrão Observer.

class Program
{
static void Main(string[] args)
{
Console.WriteLine("*** Demonstração do Padrão Observer ***\n");

IObservador observador1 = new ObservadorA("Carlos");
IObservador observador2 = new ObservadorA("Roberto");
IObservador observador3 = new ObservadorB("Fernanda");

Sujeito sujeito = new Sujeito();

sujeito.AdicionarObservador(observador1);
sujeito.AdicionarObservador(observador2);
sujeito.AdicionarObservador(observador3);

Console.WriteLine("Definindo Valor = 5");
sujeito.Valor = 5;

sujeito.RemoverObservador(observador1);

Console.WriteLine("\nDefinindo Valor = 50");
sujeito.Valor = 50;

sujeito.AdicionarObservador(observador1);

Console.WriteLine("\nDefinindo Valor = 100");
sujeito.Valor = 100;

Console.ReadKey();
}
}

Diagrama de Classes

Diagrama de Classes no Padrão Observer

IObservador

  • Contextualização: Este é o “assinante” no nosso aplicativo de notícias. Pode ser visualizado como um usuário que se inscreveu para receber atualizações sobre tópicos específicos de notícias.
  • Responsabilidade: Ouvir e reagir às atualizações do Sujeito. Cada vez que uma nova notícia é publicada, o método Atualizar do IObservador é acionado para notificar o usuário sobre a nova publicação.

ObservadorA e ObservadorB

  • Contextualização: Estes representam diferentes tipos de assinantes. Por exemplo, ObservadorA pode ser um usuário regular, enquanto ObservadorB pode ser um usuário VIP com notificações personalizadas.
  • Responsabilidade: Implementar o método Atualizar de maneiras específicas para garantir que os assinantes sejam notificados de acordo com suas preferências e status de inscrição.

ISujeito

  • Contextualização: Este é o “publicador” no nosso aplicativo de notícias, responsável por enviar atualizações aos assinantes.
  • Responsabilidade: Manter uma lista de observadores e notificá-los sempre que houver uma atualização. No contexto do nosso aplicativo de notícias, isso acontece quando uma nova notícia é publicada.

Sujeito

  • Contextualização: Representa o aplicativo de notícias em si, mantendo uma lista de todos os assinantes interessados em receber atualizações.
  • Responsabilidade: Gerenciar a lista de observadores, permitir que novos observadores se registrem, remover observadores e notificar todos os observadores registrados sobre as atualizações.

Desvendando o Código

Com esses componentes em mãos, temos um sistema onde os usuários (observadores) podem se inscrever para receber notificações do aplicativo de notícias (sujeito). Quando uma nova notícia é publicada, todos os usuários inscritos são notificados instantaneamente, garantindo que estejam sempre atualizados com as últimas notícias.

Prós e Contras

O Padrão Observer promove um design limpo e uma separação clara de responsabilidades. No entanto, pode levar a complexidades quando temos um grande número de observadores ou quando as dependências entre objetos são complicadas.

Prós

Baixo Acoplamento:

  • Contextualização: O Padrão Observer promove a separação clara entre o sujeito e os observadores. No contexto do aplicativo de notícias, isso significa que as mudanças no sistema de notificação não afetarão diretamente os usuários inscritos, e vice-versa.
  • Benefício: Facilita a manutenção e a expansão do código, pois as alterações em uma parte do sistema têm um impacto mínimo ou nulo sobre as outras.

Reatividade:

  • Contextualização: Os observadores são notificados instantaneamente sobre as mudanças no sujeito. Para os usuários do aplicativo de notícias, isso se traduz em atualizações em tempo real sempre que uma nova notícia é publicada.
  • Benefício: Melhora a experiência do usuário, garantindo que eles estejam sempre atualizados com as informações mais recentes.

Flexibilidade:

  • Contextualização: Novos observadores podem ser adicionados e removidos dinamicamente. Isso é semelhante aos usuários que podem optar por se inscrever ou cancelar a inscrição das notificações a qualquer momento.
  • Benefício: Permite que o sistema se adapte dinamicamente a diferentes necessidades e preferências dos usuários.

Contras

Complexidade:

  • Contextualização: Com um grande número de observadores, o sistema pode se tornar complexo. No aplicativo de notícias, isso pode se traduzir em desafios para gerenciar e otimizar a entrega eficiente de notificações para um grande número de usuários.
  • Desafio: Pode exigir estratégias adicionais para gerenciar, otimizar e escalar o sistema eficientemente.

Risco de Atualizações Inesperadas:

  • Contextualização: Os observadores podem receber atualizações inesperadas se o sujeito for modificado de maneira inadequada. No contexto do aplicativo de notícias, uma notificação pode ser enviada acidentalmente antes que a notícia esteja pronta para ser publicada.
  • Desafio: Necessidade de implementar controles rigorosos para garantir que as notificações sejam enviadas de maneira apropriada e precisa.

Desempenho:

  • Contextualização: A notificação de um grande número de observadores pode impactar o desempenho. No aplicativo de notícias, se milhares de usuários estiverem inscritos para receber notificações, isso pode levar a atrasos ou problemas de desempenho.
  • Desafio: Implementar soluções de otimização e escalabilidade para garantir que o sistema continue eficiente e responsivo, mesmo com um grande volume de observadores.

Conclusão

O Padrão Observer é como uma orquestra bem afinada. Cada instrumento, ou objeto, sabe exatamente quando entrar e sair, garantindo que a música flua harmoniosamente. No mundo do software, isso se traduz em sistemas reativos e responsivos, onde os objetos estão sempre atualizados e alinhados.

Para você que gostou e vem gostando de nossa série de artigos sobre Padrões de Projeto de forma rápida e objetiva, peço que curta, aplauda aqui no Medium ou até mesmo compartilhe com seus amigos e colegas de trabalho/faculdade/comunidade.

Na próxima semana, prepare-se para uma nova aventura pelo fascinante mundo dos padrões de design. Até lá! 🚀

--

--

Vinicius Climaco

Tech Speaker | Solutions Architect | Microsoft MVP | Aws Community Builder