Design Pattern – Observer (Parte 01)

Olá pessoal, tudo bem?

Hoje vamos ver sobre o Padrão de Projeto “Observer”. Caso queira ver sobre o primeiro padrão, Strategy, clique aqui. Apenas para revisar vamos ver o pequeno quadro abaixo que exibe o que já foi estudado sobre o assunto de Design Pattern:
Padrão de Projeto: Strategy
Princípios vistos:

  • Identifique tudo o que varia e separe do restante que não irá variar.
  • Programe para interface.
  • Dar prioridade à composição.

Vamos imaginar o seguinte caso de uso:
A empresa mail4Usted de mala direta tem um sistema especializado em enviar emails. A empresa tem um sistema que aplica configurações específicas para cada tipo de servidor de email.
A mail4Usted solicita que seja efetuada uma alteração em seu sistema. O problema é que a antiga empresa que dava manutenção em seu sistema sumiu do mapa, então o máximo que a mail4Usted poderá fazer é fornecer o código fonte antigo.

O sistema atualmente envia email apenas para clientes que tenham email no Yahoo, e Gmail. Foi solicitado pela empresa que seja adicionado primeiramente envio de email para o Hotmail, se a mail4Usted gostar do trabalho realizado fechará contrato para adicionar outros provedores de email.

Nosso mais novo cliente também explica que essa divisão de email é necessária, pois uma formatação especial para cada servidor de email é aplicada.”

O código que nos foi deixado é o seguinte:

public class EmailSender{
    public void sendNewEmails(){
        String emailText = getNewEmailTextFromDataBase();
        String emailSubject = getNewEmailTitleFromDataBase();

        sendEmailToGmail(emailText, emailSubject);
        sendEmailToYahoo(emailText, emailSubject);
    }

    //Others methods
}

Em uma solução pobre, bastaria implementar o método para enviar email ao Hotmail e estaríamos felizes:

public class EmailSender{
    public void sendNewEmails(){
        String emailText = getNewEmailTextFromDataBase();
        String emailSubject = getNewEmailTitleFromDataBase();

        sendEmailToGmail(emailText, emailSubject);
        sendEmailToYahoo(emailText, emailSubject);

        // Our new method
        sendEmailToHotmail(emailText, emailSubject);
    }

    //Others methods
}

Mas imagine se toda vez que um servidor de email tiver que ser adicionado ou sofrer manutenção, nossa classe principal será alterada. Ficará uma classe longa, de difícil manutenção e não muito coesa.

Para melhorar poderemos aplicar dois padrões de projeto: a começar pelo Strategy e depois Observer.

O que varia em nosso código que poderíamos isolar? Note que temos um comportamento que sempre será diferente e toda alteração que acontecer afetará nossa classe principal, algo que não precisa acontecer.

Aplicando o princípio visto no Strategy iremos separar esse comportamento. Mas para deixar ainda mais interessante, que tal entender um pouco sobre o padrão Observer para aplicar os dois juntos? Vamos ver um pequeno exemplo para nos dar uma idéia sobre como padrão Observer funciona:
Imagine que você se cadastra em uma loja para receber anúncios de promoções sobre seus produtos. A partir de então mesmo que você não tenha a mínima idéia de quando acontecerá uma promoção, você irá receber essa notificação.
Perceba que você será alvo de uma notificação de promoção mesmo estando totalmente ocupado com outras atividades. Talvez você esteja lendo um jornal, vendo TV, passeando com o cachorro e mesmo assim receberá essa notificação.

Por que não aplicarmos essa idéia aqui? Poderíamos fazer com que cada comportamento de enviar email fosse isolado (Strategy). Depois que o comportamento for isolado em uma classe, essa classe irá receber uma notificação informando que ela já tem os dados solicitados (funcionando como um bom Listener). Veja que será aplicado o padrão Observer que funciona assim: a ClasseA informa à ClasseB uma informação necessária e a ClasseB irá trabalhar com essa informação.

Poderíamos definir o padrão Observer por:
Define uma relação de dependência “Um para Muitos”. Quando o estado desse objeto mudar, todos os outros relacionados a ele serão notificados dessa mudança.
Em nosso caso utilizaremos a classe do tipo EmailSender para notificar a cada classe do tipo EmailHost que acontecerá uma nova rotina para envio de email.

Primeiro vamos ver como ficarão nossas interfaces (lembre-se de sempre programar para interfaces, sendo que interfaces significam um super-tipo!):Design Pattern Observer Parte 01Teremos a partir de agora um código que será mais flexível a mudanças, com bons padrões começando a ser aplicados.

O Listener será notificado de que alguma mudança aconteceu (método update(…)) no estado de nosso “Notifier” (que é o responsável por notificar). Após perceber essa mudança, nosso listener irá enviar o email (método sendEmail()).

Nossas classes do tipo Listener e EmailPattern:Design Pattern Observer Parte 01

Nossa classe do tipo Notifier:Design Pattern Observer Parte 01
Note que isolamos vários comportamentos que variam (Strategy). Isolamos “quem” irá enviar email (classes EmailPattern) e “quem” irá preparar e notificar essas classes (EmailSender).

Veja agora como ficarão as nossas classes implementadas:

public class EmailSender implements Notifier {
    private List listeners;
    private String emailText;
    private String emailSubject;

    public EmailSender(){
        listeners = new ArrayList();
    }

    public void addListener(Listener listener){
        listeners.add(listener)
    }

    public void removeListener(Listener listener){
        listeners.remove(listener)
    }

    public void createEmails(){
        emailText = getNewEmailTextFromDataBase();
        emailSubject = getNewEmailTitleFromDataBase();

        notifyListeners();
    }

    public void notifyListeners(){
        for(Listener listener : listeners){
            listener.update(emailText, emailSubject);
        }
    }

    //Others methods
}
// All classes would have almost the same implementation with their own peculiarities.
public class GmailPattern implements Listener, EmailPattern {
    public void update(String emailText, String emailSubject){
        // apply class peculiarities
        sendEmail();
    }

    public void sendEmail(){
        // send the email
    }
}

Coloquei apenas o código do GmailPattern descrito acima para facilitar na leitura. Todas as outras classes como HotmailPattern, YahooPattern teriam os mesmos métodos variando apenas seu comportamento.

Veja como fica mais simples, coeso e funcional. Independente de qual o tipo de padrão do email a ser enviado (Gmail, Hotmail, Yahoo), nossa classe Notifier está notificando ao seu Listener que houve uma alteração de estado. Ela não precisa saber mais se agora estamos enviando para Gmail, Yahoo ou Hotmail. Viu como a classe EmailSender continuar realizando seu trabalho e de um modo mais “limpo”?

Repare que caso seja necessário atualizar o modo como a classe HotmailPattern envia o email, nossa classe EmailSender não sofrerá qualquer tipo de alteração. Isolando esse comportamento estamos tornando nosso programa mais flexível a mudanças.

Talvez você se pergunte: “Qual a vantagem do padrão Observer? Onde aplicar isso?” Vou colocar abaixo alguns exemplos:

  • Toda vez que um carro furar sinal vermelho, uma máquina irá tirar uma foto e enviar para o sistema. Teve que ter “alguém” que observou o fato e notificou ao sistema o que aconteceu.
  • Uma aplicação de mensagens online. Por exemplo: o antigo ICQ (mas vale para os atuais também), você tem a aplicação rodando em seu computador. Ao selecionar um contato de sua lista, você pode digitar uma mensagem e enviar. O ICQ em sua máquina notifica ao servidor essa mensagem. Ou o ICQ do contato busca no servidor se existem mensagens ou o servidor notifica o ICQ na máquina do usuário. Uma coisa é certa, não importa o que o seu contato esteja fazendo: “lendo fofoca de novela”, “vendo desenhos do Tom e Jerry no Youtube”, ele irá receber a mensagem.
  • Um mais simples ainda é: controle remoto. Existe uma aplicação que está aguardando o estímulo do controle. Assim que você pressiona o botão a aplicação entende a mensagem enviada e faz o que foi solicitado.

Mas ainda existe um acoplamento muito forte entre nossa classe EmailSender e suas listeners (GmailPattern,…). Olhe novamente o código mais acima e tente imaginar…

Para deixar esse acoplamento mais claro, vamos imaginar mais um requisito em nosso sistema:
Imagine que agora além da informação do “assunto” e do “conteúdo do texto” será necessário passar qual a conta do Yahoo usar. Com isso teremos um listener com um comportamento diferente dos demais.
Toda vez que precisarmos alterar a quantidade de parâmetros para enviar emails um Listener, YahooPattern por exemplo, terá que ser alterado e também um Notifier (EmailSender) sofrerá alterações.

Como melhorar isso? O ideal não é fazer com que alteração em uma classe não afete outra?

Veremos no próximo post sobre o assunto de Design Pattern outra solução para o tipo Observer.

Até a próxima pessoal. o_

Revisado por: Renan Alves dos Santos

Leave a Comment