.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;
- Uma classe com acoplamento fraco não é dependente de muitas classes para fazer o que ele tem que fazer;
- Uma classe com acoplamento forte depende de muitas outras classes para fazer o seu serviço;
- Uma classe com
acoplamento forte é mais difícil de manter, de entender e de ser reusada;

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:
  1. Sujeito (subject):  Conhece os seus observadores. Um número qualquer de observadores é permitido.  Provê uma interface para adicionar/remover observadores;
     
  2. Observador (observer): Define uma interface de atualização para os objetos que devem ser notificados sobre as mudanças no sujeito;
     
  3. Sujeito concreto (ConcreteSubject) - Armazena o estado de interesse para os objetos de ConcreteObserver;
    Envia uma notificação aos observadores concretos quando seu estado mudar;
     
  4. Observador concreto (ConcreteObserver) - Implementa a interface definida pelo Observador para tratar as notificações;

O mesmo diagrama de classes traduzido:

Vantagens em usar o padrão Observer:

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:

  1. Sujeito - Interface que define os métodos para Registrar/Remover/Notificar um observador;
  2. Observador - Interface que define o método Atualizar() que informa o observador quando houver uma alteração do estado do Sujeito;
  3. EditoraConcreta - Classe concreta que implementa interface Sujeito;
  4. AssinanteConcreto - Classe concreta que implementa a interface Observador;

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,
1Pe 2:16
como livres, e não tendo a liberdade como capa da malícia, mas como servos de Deus.
1Pe 2:17
Honrai a todos. Amai aos irmãos. Temei a Deus. Honrai ao rei.
1Pe 2:18
Vós, servos, sujeitai-vos com todo o temor aos vossos senhores, não somente aos bons e moderados, mas também aos maus.

1Pe 2:19
Porque isto é agradável, que alguém, por causa da consciência para com Deus, suporte tristezas, padecendo injustamente.

Referências:


José Carlos Macoratti