.NET - O padrão de projeto Observer
O padrão de projeto (Design Pattern) Observer é um padrão comportamental que representa uma relação de 1-N (de um para muitos) entre objetos. Assim quando um objeto altera o seu estado os objetos dependentes serão notificados/informados/avisados e atualizados de forma automática. |
O padrão possibilita que objetos sejam avisados da mudança de estado de outros eventos ocorrendo em outro objeto.
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.
As expressões "acoplamento
fraco" ou "acoplamento forte"
são comuns em quase todas as discussões sobre projeto
de software. O acoplamento entre classes ou subsistemas é uma medida da interconexão entre essas classes ou subsistemas. O acoplamento forte significa que as classes relacionadas precisam conhecer detalhes internos umas das outras, as alterações se propagam pelo sistema, e o sistema é potencialmente mais difícil de entender e manter. -
Acoplamento é o nível de dependência/conhecimento que
pode existir entre as classes; |
Em uma definição mais formal um padrão Observer permite:
Definir 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." [GoF]
Nota: Padrões de projeto comportamentais são padrões que tratam das interações e divisões de responsabilidades entre as classes ou objetos.
O diagrama de classes para o padrão Observer é mostrado na figura abaixo:
Classes/Objetos
participantes do padrão:
|
O mesmo diagrama de classes traduzido:
Vantagens em usar o padrão Observer:
- Pode reutilizar Sujeitos sem reutilizar os seus observadores e vice-versa;
- Os Observadores podem ser adicionados sem modificar o Sujeito;
- Todo sujeito conhece a sua lista de Observadores;
- O Sujeito não precisa conhecer a classe concreta de um observador, apenas que cada observador implementa a interface update();
- O Subjeito e Observador podem pertencer a diferentes camadas de abstração;
Quando usar o padrão Observer ?
Questões
de Implementação do padrão Observer:
Como é que o sujeito vai manter o controle de seus
observadores?
R: Usando um
Array, uma lista ligada, etc..
E o que ocorre se um observador quer observar mais de um
sujeito?
R: Ele têm
que dizer ao observador quem ele é através da interface de
atualização.
E quem aciona a atualização?
R: O
sujeito, sempre que ele muda de estado;
Os observadores depois de causar uma ou mais
mudanças de estado;
Algum outro objeto;
Obs:
Verifique se o Sujeito atualiza o seu estado antes de enviar
notificações;
Quanta informação sobre a mudança deve o Sujeito enviar
para os observadores?
R: No modelo
Push - bastante ou no modelo Pull -
muito pouca;
Um observador pode ser um Sujeito
?
R: Sim pode;
Implementando o padrão de projeto Observer
Este exemplo foi baseado no livro Use a Cabeça (head first) Padrões de Projeto, FREEMAN (2007:81-96).
Para implementar o padrão Observer eu vou usar o seguinte cenário:
Neste cenário iremos ter as seguintes classes:
O Diagrama de classes gerado é visto abaixo:
Implementando o padrão Observer
Vamos agora criar as classes e verificar o seu funcionamento.
Eu vou usar o Visual Studio 2012 Express Edition e criar uma aplicação Console chamada PadraoProjetoObserver;
Obs: Estou disponibilizando também o código para VB .NET criado no Visual Studio 2012 Express Edition
No menu Project clique em Add Class e informe o nome Sujeito.cs/Sujeito.vb e a seguir digite o código da classe Sujeito conforme abaixo:
namespace PadraoProjetoObserver { interface Sujeito { void RegistrarObservador(Observador o); void RemoverObservador(Observador o); void NotificarObservadores(); } } |
C# |
Public Interface Sujeito Sub RegistrarObservador(o As Observador) Sub RemoverObservador(o As Observador) Sub NotificarObservadores() End Interface |
VB .NET |
A interface Sujeito de declara os métodos para registrar/remover e notificar um observador;
namespace PadraoProjetoObserver { interface Observador { void Atualizar(Sujeito sujeito); } } |
C# |
Public Interface Observador Sub Atualizar(sujeito As Sujeito) End Interface |
VB .NET |
A interface Observador declara o método Atualizar;
using System; using System.Collections.Generic; namespace PadraoProjetoObserver { class EditoraConcreta : Sujeito { private List<Observador> observadores = new List<Observador>(); private bool _novaEdicao = false; public void RegistrarObservador(Observador o) { observadores.Add(o); } public void RemoverObservador(Observador o) { observadores.Remove(o); } public void NotificarObservadores() { foreach (Observador o in observadores) { o.Atualizar(this); } } public void alterarEdicao() { if (_novaEdicao) _novaEdicao = false; else _novaEdicao = true; NotificarObservadores(); } public Boolean getEdicao() { return _novaEdicao; } } } |
Exemplo C# |
IImports System.Collections.Generic Public Class EditoraConcreta Implements Sujeito Private observadores As New List(Of Observador)() Private _novaEdicao As Boolean = False Public Sub NotificarObservadores() Implements Sujeito.NotificarObservadores For Each o As Observador In observadores o.Atualizar(Me) Next End Sub Public Sub RegistrarObservador(o As Observador) Implements Sujeito.RegistrarObservador observadores.Add(o) End Sub Public Sub RemoverObservador(o As Observador) Implements Sujeito.RemoverObservador observadores.Remove(o) End Sub Public Sub alterarEdicao() If _novaEdicao Then _novaEdicao = False Else _novaEdicao = True End If NotificarObservadores() End Sub Public Function getEdicao() As [Boolean] Return _novaEdicao End Function End Class |
Exemplo VB .NET |
Note que na classe concreta EditoraConcreta estamos chamando o método NotificarObservadores() após ocorrer uma mudança de estado no objeto EditoraConcreta;
Na implementação do método notificarObservadores percorremos os observadores informando cada instância dele mesmo e informando que o objeto que estava sendo observado mudou seu estado.
using System; namespace PadraoProjetoObserver { class AssinanteConcreto : Observador { private EditoraConcreta objetoObservado; public AssinanteConcreto(EditoraConcreta o) { objetoObservado = o; objetoObservado.RegistrarObservador(this); } public void Atualizar(Sujeito sujeito) { if (sujeito == objetoObservado) { Console.WriteLine("[Aviso] - A editora alterou o seu estado para : " + objetoObservado.getEdicao()); } } } } |
C# |
Public Class AssinanteConcreto Implements Observador Private objetoObservado As EditoraConcreta Public Sub New(o As EditoraConcreta) objetoObservado = o objetoObservado.RegistrarObservador(Me) End Sub Public Sub Atualizar(sujeito As Sujeito) Implements Observador.Atualizar If sujeito.Equals(objetoObservado) Then Console.WriteLine("[Aviso] - A editora alterou o seu estado para : " & objetoObservado.getEdicao()) End If End Sub End Class |
VB .NET |
No construtor da classe AssinanteConcreto criamos o observador e já definimos como parâmetro o objeto observado, logo a seguir podemos chamar o método adicionarObservador que passa por referência a sua própria instância.
Quando o método atualizar é chamado nós precisamos verificar se a editora que alterou o estado é a mesma que estamos observando.
using System; namespace PadraoProjetoObserver { class Program { static void Main(string[] args) { EditoraConcreta editora = new EditoraConcreta(); // Editora ja inicia com valor padrão igual a false AssinanteConcreto assinante1 = new AssinanteConcreto(editora); AssinanteConcreto assinante2 = new AssinanteConcreto(editora); AssinanteConcreto assinante3 = new AssinanteConcreto(editora); AssinanteConcreto assinante4 = new AssinanteConcreto(editora); AssinanteConcreto assinante5 = new AssinanteConcreto(editora); // Já passando a editora como parametro editora.alterarEdicao(); // Nesse momento é chamado o método atualizar // das instâncias assinante1 e assinante2, resultadao: // [Aviso] A Editora mudou seu estado para: true (5 x) editora.alterarEdicao(); //[Aviso] A Editora mudou seu estado para: false (5 x) // Obs: temos 5 saídas porque temos 5 assinantes Console.ReadKey(); } } } |
C# |
Module Module1 Sub Main() Dim editora As New EditoraConcreta() ' Editora ja inicia com valor padrão igual a false Dim assinante1 As New AssinanteConcreto(editora) Dim assinante2 As New AssinanteConcreto(editora) Dim assinante3 As New AssinanteConcreto(editora) Dim assinante4 As New AssinanteConcreto(editora) Dim assinante5 As New AssinanteConcreto(editora) ' Já passando a editora como parametro editora.alterarEdicao() ' Nesse momento é chamado o método atualizar ' das instâncias assinante1 e assinante2, resultadao: ' [Aviso] A Editora mudou seu estado para: true (5 x) editora.alterarEdicao() '[Aviso] A Editora mudou seu estado para: false (5 x) ' Obs: temos 5 saídas porque temos 5 assinantes Console.ReadKey() End Sub End Module |
VB .NET |
Poderíamos dar nomes aos assinantes usando uma string em AssinanteConcreto e passar seu nome para o construtor como segundo parâmetro.
O resultado acima mostra a notificação emitida a todos os assinantes registrados após a mudança de estado.
Pegue a solução completa aqui: PadraoProjetoObserver.zip / PadraoProjetoObserverVB.zip
1Pe 2:15
Porque assim é a vontade de Deus, que, fazendo o bem, façais emudecer a ignorância dos homens insensatos,Referências: