C# - Padrão Comportamental Gof - Observer (revisitado)
Neste artigo vamos recordar o padrão comportamental Gof Observer. |
Segundo a
definição da Gang Of Four(GoF), o padrão
Observer define uma dependência um-para-muitos
entre objetos para que, quando um objeto mudar de estado, todos os seus
dependentes sejam notificados e atualizados automaticamente.
Este padrão permite que um objeto notifique outros objetos sobre alterações em
seu estado.
Neste padrão temos
um objeto (chamado de Subject) mantém uma
lista de seus dependentes (chamados de Observers)
e os notifica automaticamente sempre que algum estado muda, chamando um de seus
métodos.
Podemos dizer de forma simples que o objetivo principal do padrão
Observer é manter a consistência entre objetos
relacionados sem criar um código fortemente acoplado.
Então o padrão Observer é útil quando precisamos que dois ou mais objetos "escutem"
a determinados eventos em um outro objeto.
Os objetos que estão escutando são conhecidos como Observers e o objeto que é escutado (ou observado) é conhecido como Subject.
Dessa forma
podemos dizer que o padrão Observer tem dois
componentes principais:
1- Subject
São objetos publicadores (publishers) –
Quando ocorre alguma mudança no estado de um Subject
ele deve notificar todos os seus assinantes (subscribers)
2- Observers
Eles são os assinantes (subscribers). Eles
simplesmente ouvem as mudanças dos subjects.
Uma tradução livre para Subject seria Sujeito e para Observer seria Observadores eu vou usar os nomes no idioma inglês.
Observer : Exemplo
Vejamos um exemplo para entender o padrão Observer.
Suponha que temos
um Subject (que tem um estado) e alguns
Observers:
Neste cenário, os objetos Observers querem
ser atualizados quando algo mudar no Subject.
Então para isso a cada 10s eles enviam uma solicitação ao Subject se algo
mudou, e, assim o objeto Subject tem que ser conhecido pelos objetos Observers.
Temos aqui que muitos Observers dependem de um
Subject (definindo o
relacionamento de muitos para um).
Percebemos que o
problema aqui é manter os objetos Observers atualizados com relação as mudanças
que o objeto Subject publica.
Agora vamos supor que o intervalo entre as mudanças seja de 60 segundos.
Neste intervalo cada objeto Observer já realizou 6 solicitações ao objeto
Subject sendo que somente a última retornará com uma resposta positiva e as
demais foram desnecessárias.
Outro problema aqui é a quantidade de objetos Observers quando mais objetos
Observers existirem maior será a quantidade
de solicitações não necessárias. Assim sem usar o padrão Observer os
observadores tem que ficar enviando solicitações ao subject em intervalos de
tempo o que pode consumir recursos e não ser eficiente.;
Para resolver este problema podemos usar o padrão Observer.
A proposta do padrão Observer é que os objetos Observers devem pedir ao Subject
que os notifiquem quando algo mudar.
Assim teremos o
objeto Subject e os objetos Observers mas agora o objeto Subject é quem notifica
os observers quando houver alguma mudança. E assim agora temos que
um Subject depende de muitos Obsevers (dependência um-para-muitos)
Mas para isso funcionar é preciso que haja um acordo entre o Subject e os
Observers. Onde os Observers :
- Precisam fornecer um canal para que o Subject os
notifiquem.
- Precisam conhecer o Subject que desejam observar
E o Subject :
- Precisa fornecer um canal para que os observers possam
se cadastrar como observadores.
- E também um canal para que os Observers possam se descadastrar como
observadores.
- Deve ser capaz de notificar todos os observers quando sofrer uma atualização
Para isso o Subject deve manter uma lista dos Observers que se
inscreveram para serem notificados das mudanças. E agora sempre que ocorrer
alguma mudança no seu estado o Subject envia uma notificação para todos os
inscritos na lista. Com isso não existem mais o problema das solicitações
desnecessárias.
Com isso o uso do padrão Observer fornece uma forma de reagir a eventos que
acontecem em objetos sem ter um forte acoplamento em suas classes.
A ideia fundamental por trás do padrão do observador é centralizar a tarefa de
informar dentro do Subject. Portanto, ele mantém uma lista na qual os
observadores podem se cadastrar para ingressar. Em caso de alteração, o subject
informa os observadores cadastrados - um após o outro - sem que eles próprios
tenham que agir.
Se uma atualização automática de status não for mais desejada para um
determinado objeto observador, ela pode simplesmente ser removida da lista.
Diagrama UML
O diagrama UML do padrão Iterator segundo o Gof apresenta os seguintes participantes
1- Subject -
- Conhece seus
Observers;
- Fornece uma interface para anexar e desanexar objetos Observer;
- Qualquer número de objetos Observer pode observar um Subject;
2- ConcreteSubject
- Armazena o
estado de interesse para ConcreteObserver;
- Envia uma notificação para seus observadores quando seu estado muda;
3- Observer - Def
- Define uma interface de atualização para objetos que devem ser notificados das mudanças em um Subject;
4- ConcreteObserver - I
- Mantém uma
referência a um objeto ConcreteSubject;
-Implementa a interface de atualização do Observer para manter seu estado
consistente com o do Subject;
- Armazena o estado que deve permanecer consistente com o do Subject;
Quando podemos usar o padrão
Podemos usar o padrão Iterator nos seguintes cenários :
- Quando uma
modificação do estado de um objeto implica modificações em outros objetos;
- Quando um objeto deve ser capaz de notificar outros objetos, mas sem
pressupostos sobre os objetos a serem notificados;
- Quando uma abstração possuir dois aspectos e um depende do outro;
- Quando não desejamos um forte acoplamento com os objetos que necessitam
conhecer estas modificações;
Vantagens do padrão
Como vantagens deste padrão temos que :
-Permite um
acoplamento mínimo entre o Subject e o Observer;
-Pode reutilizar Subjects sem reutilizar os seus Observers e vice-versa;
-Os Observers podem ser adicionados sem modificar o Subject;
-O Subject e Observer podem pertencer a diferentes camadas de abstração;
-O Subjeito e Observador podem pertencer a diferentes camadas de abstração;
Desvantagem
E como desvantagem
podemos citar que :
Os assinantes são notificados em ordem aleatória. Também pode ocorrer o problema
de vazamento de memória devido ao registro explícito e ao cancelamento do
registro do Observer.
Aplicação prática do padrão
Como exemplo de
aplicação do padrão vamos imaginar o seguinte cenário:
1- 3 usuários visitam um site de vendas on line para comprar o mesmo celular -
um IPhone 11 :
Acontece que no momento este celular não esta disponível ou seja a loja esta sem
estoque e assim não é possível comprar o produto no momento.
Para estes casos o site oferece uma opção para notificar o usuário quando o
produto estiver disponível. Para isso os usuários tem que se registrar no
serviço marcando a opção para serem notificados quando o celular chegar.
Assim quando o celular IPhone 11 chegar os usuários
receberam uma notificação do site.
Então após algum
tempo a loja do site recebeu o produto e o seus status passou de - Sem Estoque -
para - Disponível - e com isso o site enviou notificações para todos os usuários
que se registraram no serviço.
Aqui podemos identificar os dois componentes principais do padrão
Observer :
- O Celular IPhone 11 representa o Subject;
- Os usuários são os Observers;
E assim de acordo com o padrão Observer, os Observers precisam estar registrados
para o Subject. No nosso caso, os três usuários estão cadastrados para a
opção de notificação do Subject. Quando o estado do Subjcet mudar de -
Sem Estoque - para Disponível - o
Subject enviará uma notificação a todos os observers.
Temos aqui um exemplo de atuação do padrão observer a qual iremos implementar
via código usando o C#.
Implementação prática
Levando em conta este cenário vamos implementar o padrão Observer usando uma aplicação Console .NET Core (.NET 5.0) criada no VS 2019 Community.
A seguir temos o diagrama de classes obtido a partir do VS 2019 na implementação do padrão:
1-
ISubject - Define o contrato para registrar,
remover e notificar Observers;
2 -ConcreteSubject - Define uma lista de observers
e o produto cujo estado será observado e cria um objeto produto e define seu
status inicial (subject);
- define o método para retornar a disponibilidade atual do produto;
- Altera o status do produto definindo sua disponibilidade atual;
- Registrar um observer interessado em recebe notificações sobre atualização do
status de produto;
- Define um método para incluir o observer na lista de observadores;
- Define um método para remover o observer da lista de observadores;
- Notifica os observers cadastrados na lista de observadores;
3- IObserver - Define o contrato para
atualizar os objetos que devem ser notificados das mudanças no status do produto
4- ConcreteObserver - Implementa a interface de
atualização do Observer e mantêm referencia ao objeto ConcreteSubject;
A seguir temos o código usado na implementação:
1- A interface ISubject
public
interface ISubject { void RegistrarObserver(IObserver observer); void RemoverObserver(IObserver observer); void NotificarObservers(); } |
2- A Interface IObserver
public
interface IObserver { void Atualiza(string disponibilidade); } |
3- Classe ConcreteSubject
using System; using System.Collections.Generic; namespace ObserverExemplo
{
public class ConcreteSubject : ISubject
{
private List<IObserver> observers = new List<IObserver>();
private string Produto { get; set; }
private int Preco { get; set; }
private string Disponibilidade { get; set; }
public ConcreteSubject(string produto, int preco, string status)
{
Produto = produto;
Preco = preco;
Disponibilidade = status;
}
public string GetDisponibilidade()
{
return Disponibilidade;
}
public void SetDisponibilidade(string status)
{
this.Disponibilidade = status;
Console.WriteLine("A disponibilidade mudou de 'Sem Estoque' para 'Disponível'.");
NotificarObservers();
}
public void RegistrarObserver(IObserver observer)
{
Console.WriteLine("Observer Adicionado : " + ((ConcreteObserver)observer).Usuario);
observers.Add(observer);
}
public void AdicionarObservers(IObserver observer)
{
observers.Add(observer);
}
public void RemoverObserver(IObserver observer)
{
observers.Remove(observer);
}
public void NotificarObservers()
{
Console.WriteLine($"O Produto :{Produto} no valor de R$ {Preco} agora esta disponível." +
"\n### Notificando todos os Observers registrados ### ");
Console.WriteLine();
foreach (IObserver observer in observers)
{
observer.Atualiza(Disponibilidade);
}
}
}
}
|
4- Classe ConcreteObserver
using System;
namespace ObserverExemplo
{
public class ConcreteObserver : IObserver
{
public string Usuario { get; set; }
public ConcreteObserver(string nome, ISubject subject)
{
Usuario = nome;
subject.RegistrarObserver(this);
}
public void Atualiza(string disponibilidade)
{
Console.WriteLine($"Olá {Usuario}, o Produto que você deseja agora " +
$"esta {disponibilidade} em nosso site");
}
}
}
|
7- Program
|
A execução do projeto irá apresentar o seguinte resultado:
O padrão Observer talvez seja um dos padrões de projetos mais usados, principalmente para aplicações web. Conhecer e saber aplicar os conceitos envolvidos neste padrão deve ser um quesito imprescindível para todo o desenvolvedor.
Pegue o código do projeto aqui : ObserverExemplo.zip
"Porque, se
alguém cuida ser alguma coisa, não sendo nada, engana-se a si mesmo. Mas prove
cada um a sua própria obra, e terá glória só em si mesmo, e não noutro."
Gálatas 6:3,4
Referências:
NET - Unit of Work - Padrão Unidade de ...
NET - O padrão de projeto Decorator
NET - Padrão de Projeto Builder
C# - O Padrão Strategy (revisitado)
NET - O padrão de projeto Command
NET - Apresentando o padrão Repository
.NET - Design Patterns - Uma abordagem Prática
.NET - Usando padrões de projeto e princípios OOP na prática
.NET - Padrão de Projeto Builder
Design Patterns - Identificando e Aplicando padrões
.NET - O padrão de projeto Decorator