Exercício

Dado o seguinte grafo que representa pessoas que seguem pessoas no Twitter.

Figura 1

A classe Pessoa abaixo representa uma pessoa (um nó desse grafo) e uma aresta direcionada do nó A para o nó B significa que B segue A.

Cada vez que o método twita é acionado em uma Pessoa X, o tweet passado como parâmetro deve ser entregue para todos os que seguem X.

Implemente esse mecanismo de entrega de tweets usando o pattern Observer. Se necessário, modifique a classe Pessoa e implemente novas classes e interfaces.

Escreva um código que teste alguns tweets.


In [1]:
public abstract class Pessoa {
    private String nome;
    
    public Pessoa(String nome) {
        this.nome = nome;
    }
   
    public String getNome() {
        return nome;
    }
    
    public abstract void twita(String tweet);
}

Resolução

Algumas decisões importantes:

  • Não foi especificado o que o seguidor devia fazer com o tweet, portanto a solução dessa parte é aberta (decidi imprimir no console).
  • O método twita pode fazer o papel do notify ou pode acionar o notify. Ambas as alternativas são igualmente corretas e a escolha (em outras situações) irá depender do contexto, por exemplo, separar o notify permitiria que outros métodos o utilizassem no futuro. Aqui escolhi deixar o mesmo método para fins de simplificação.

In [13]:
public interface IObserver {
    public void enviaTweet(String tweet);
}

public interface ISubject {
    public void attach(IObserver seguidor);
    public abstract void twita(String tweet);
}

public class PessoaTwitter extends Pessoa implements IObserver, ISubject {
    private ArrayList<IObserver> seguidores = new ArrayList<IObserver>();
    
    public PessoaTwitter(String nome) {
        super(nome);
    }
    
    public void enviaTweet(String tweet) {
        // faz alguma coisa com o tweet (opcional)
        System.out.println(getNome() + " recebeu: " + tweet);
    }
    
   public void attach(IObserver seguidor) {
      seguidores.add(seguidor);
   }
    
   public void twita(String tweet) {
      for (IObserver segue: seguidores)
          segue.enviaTweet(tweet);
   }    
}

ISubject observado = new PessoaTwitter("Asdrubal");
IObserver observador1 = new PessoaTwitter("Doriana");
observado.attach(observador1);
IObserver observador2 = new PessoaTwitter("Melissa");
observado.attach(observador2);
observado.twita("O dinossauro pulou na lama.");


Doriana recebeu: O dinossauro pulou na lama.
Melissa recebeu: O dinossauro pulou na lama.

Exercício

Considere a seguinte representação em XML:

  • <PESSOA></PESSOA> - Há um elemento (tag) desse para cada pessoa, com um atributo nome contendo o nome da pessoa como o exemplo:
    <PESSOA nome="A">
    </PESSOA>
    <PESSOA nome="B">
    </PESSOA>
    
  • <SEGUIDOR/> - Há um elemento desse cada vez que uma Pessoa X segue uma Pessoa Y. Nesse exemplo o elemento <SEGUIDOR> fica subordinado à Pessoa X e tem um atributo nome indicando que a Pessoa Y é seguidor da Pessoa X. Veja a seguir o XML correspondente ao grafo da Figura 1:
    <PESSOA nome="A">
      <SEGUIDOR nome="B"/>
      <SEGUIDOR nome="C"/>
      <SEGUIDOR nome="D"/>
    </PESSOA>
    <PESSOA nome="B">
      <SEGUIDOR nome="E"/>
    </PESSOA>
    <PESSOA nome="C">
      <SEGUIDOR nome="E"/>
    </PESSOA>
    <PESSOA nome="D">
      <SEGUIDOR nome="F"/>
    </PESSOA>
    <PESSOA nome="E">
    </PESSOA>
    <PESSOA nome="F">
    </PESSOA>
    

Parte 1

Usando Pipe&Filter escreva um método que receba como parâmetro um objeto da classe Pessoa ou herdeira e a serialize.

Resolução

As interfaces foram modificadas para suportar serialização. Desse modo, foi possível realizar o processo a partir das interfaces, tornando a resolução mais independente (isso não era obrigatório, o método podia ter acesso a classe, como solicitado).

Foi feita a escolha de um método estático, mas ele poderia igualmente não ser estático.

Veja à esqueda que foi criado o arquivo serializado.xml.


In [12]:
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public interface IObserver {
    public void enviaTweet(String tweet);

    // acrescentado para serializacao
    public String getNome();
}

public interface ISubject {
    public void attach(IObserver seguidor);
    public abstract void twita(String tweet);

    // acrescentado para serializacao
    public String getNome();
    public ArrayList<IObserver> getSeguidores();
}

public class PessoaTwitter extends Pessoa implements IObserver, ISubject {
    private ArrayList<IObserver> seguidores = new ArrayList<IObserver>();
    
    public PessoaTwitter(String nome) {
        super(nome);
    }
    
    public ArrayList<IObserver> getSeguidores() {
        return seguidores;
    }
    
    public void enviaTweet(String tweet) {
        // faz alguma coisa com o tweet (opcional)
        System.out.println(getNome() + " recebeu: " + tweet);
    }
    
   public void attach(IObserver seguidor) {
      seguidores.add(seguidor);
   }
    
   public void twita(String tweet) {
      for (IObserver segue: seguidores)
          segue.enviaTweet(tweet);
   }    
}


public class Serializador {
    public static void serializa(ISubject pessoa) {
        PrintWriter arquivo;

        try {
            arquivo = new PrintWriter(new FileWriter("serializado.xml"));
            arquivo.println("<PESSOA nome=\"" + pessoa.getNome() + "\">");
            ArrayList<IObserver> seguidores = pessoa.getSeguidores();
            for (IObserver seguidor: seguidores)
                arquivo.println("  <SEGUIDOR nome=\"" + seguidor.getNome() + "\"/>");
            arquivo.println("</PESSOA>");
            arquivo.close();
            System.out.println("Gravacao realizada com sucesso!");
        } catch (IOException erro) {
            System.out.println("Nao consegui criar o arquivo =(");
        }        
    }
}

ISubject observado = new PessoaTwitter("Asdrubal");
IObserver observador1 = new PessoaTwitter("Doriana");
observado.attach(observador1);
IObserver observador2 = new PessoaTwitter("Melissa");
observado.attach(observador2);
observado.twita("O dinossauro pulou na lama.");
Serializador.serializa(observado);


Doriana recebeu: O dinossauro pulou na lama.
Melissa recebeu: O dinossauro pulou na lama.
Gravacao realizada com sucesso!

Parte 2

Modifique o método da Parte 1 de modo que se comporte sob o pattern DAO.

Resolução

Para seguir o pattern DAO o método de serialização foi transferido para dentro da classe PessoaTwitter.

Veja à esqueda que foi criado o arquivo serializado-dao.xml.


In [14]:
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public interface IObserver {
    public void enviaTweet(String tweet);

    // acrescentado para serializacao
    public String getNome();
}

public interface ISubject {
    public void attach(IObserver seguidor);
    public abstract void twita(String tweet);

    // acrescentado para serializacao
    public void serializa(PrintWriter saida);
}

public class PessoaTwitter extends Pessoa implements IObserver, ISubject {
     private ArrayList<IObserver> seguidores = new ArrayList<IObserver>();
    
     public PessoaTwitter(String nome) {
         super(nome);
     }
    
     public ArrayList<IObserver> getSeguidores() {
         return seguidores;
     }
    
     public void enviaTweet(String tweet) {
         // faz alguma coisa com o tweet (opcional)
         System.out.println(getNome() + " recebeu: " + tweet);
     }
    
    public void attach(IObserver seguidor) {
        seguidores.add(seguidor);
    }
    
    public void twita(String tweet) {
        for (IObserver segue: seguidores)
            segue.enviaTweet(tweet);
    }

    public void serializa(PrintWriter saida) {
        saida.println("<PESSOA nome=\"" + getNome() + "\">");
        for (IObserver seguidor: seguidores)
            saida.println("  <SEGUIDOR nome=\"" + seguidor.getNome() + "\"/>");
        saida.println("</PESSOA>");
    }
}    

ISubject observado = new PessoaTwitter("Asdrubal");
IObserver observador1 = new PessoaTwitter("Doriana");
observado.attach(observador1);
IObserver observador2 = new PessoaTwitter("Melissa");
observado.attach(observador2);
observado.twita("O dinossauro pulou na lama.");
try {
    PrintWriter arquivo = new PrintWriter(new FileWriter("serializado-dao.xml"));
    observado.serializa(arquivo);
    arquivo.close();
    System.out.println("Gravacao realizada com sucesso!");
} catch (IOException erro) {
    System.out.println("Nao consegui criar o arquivo =(");
}


Doriana recebeu: O dinossauro pulou na lama.
Melissa recebeu: O dinossauro pulou na lama.
Gravacao realizada com sucesso!

Parte 3

Modifique o processo de serialização de forma que ele se comporte de forma recursiva, ou seja, dado uma Pessoa a ser serializada, ela também dispara o processo para a serialização de todos os seus assinantes, que por sua vez recursivamente também disparam a serialização de seus assinantes e assim sucessivamente.

Considere que o grafo é acíclico.

Veja como seria serializado um pedaço do grafo da Figura 1 se fosse disparada a serialização da Pessoa A:

<PESSOA nome="A">
    <SEGUIDOR nome="B"/>
    <SEGUIDOR nome="C"/>
    <SEGUIDOR nome="D"/>
</PESSOA>
<PESSOA nome="B">
    <SEGUIDOR nome="E"/>
</PESSOA>
<PESSOA nome="C">
    <SEGUIDOR nome="E"/>
</PESSOA>
<PESSOA nome="D">
    <SEGUIDOR nome="F"/>
</PESSOA>
<PESSOA nome="E">
</PESSOA>
<PESSOA nome="F">
</PESSOA>

Resolução

Em termos de código, foi acrescentada apenas uma chamada recursiva de serialização aos seguidores. Para simplificar a construção do grafo, foi criada uma interface que unifica as interfaces de observador e assunto.

Veja à esqueda que foi criado o arquivo serializado-dao-recursivo.xml.


In [6]:
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

public interface IObserver {
    public void enviaTweet(String tweet);

    // acrescentado para serializacao
    public String getNome();
    
    // acrescentado para a serializacao recursiva
    public void serializa(PrintWriter saida);
}

public interface ISubject {
    public void attach(IObserver seguidor);
    public abstract void twita(String tweet);

    // acrescentado para serializacao
    public void serializa(PrintWriter saida);
}

public interface IObserverSubject extends IObserver, ISubject {}

public class PessoaTwitter extends Pessoa implements IObserverSubject {
     private ArrayList<IObserver> seguidores = new ArrayList<IObserver>();
    
     public PessoaTwitter(String nome) {
         super(nome);
     }
    
     public ArrayList<IObserver> getSeguidores() {
         return seguidores;
     }
    
     public void enviaTweet(String tweet) {
         // faz alguma coisa com o tweet (opcional)
         System.out.println(getNome() + " recebeu: " + tweet);
     }
    
    public void attach(IObserver seguidor) {
        seguidores.add(seguidor);
    }
    
    public void twita(String tweet) {
        for (IObserver segue: seguidores)
            segue.enviaTweet(tweet);
    }

    public void serializa(PrintWriter saida) {
        saida.println("<PESSOA nome=\"" + getNome() + "\">");
        for (IObserver seguidor: seguidores) {
            saida.println("  <SEGUIDOR nome=\"" + seguidor.getNome() + "\"/>");
        }
        saida.println("</PESSOA>");
        for (IObserver seguidor: seguidores)
            seguidor.serializa(saida);
    }
}

IObserverSubject a = new PessoaTwitter("A");
IObserverSubject b = new PessoaTwitter("B");
IObserverSubject c = new PessoaTwitter("C");
IObserverSubject d = new PessoaTwitter("D");
IObserverSubject e = new PessoaTwitter("E");
IObserverSubject f = new PessoaTwitter("F");
a.attach(b);
a.attach(c);
a.attach(d);
b.attach(e);
c.attach(e);
d.attach(f);

a.twita("O dinossauro pulou na lama.");

try {
    PrintWriter arquivo =
        new PrintWriter(new FileWriter("serializado-dao-recursivo.xml"));
    a.serializa(arquivo);
    arquivo.close();
    System.out.println("Gravacao realizada com sucesso!");
} catch (IOException erro) {
    System.out.println("Nao consegui criar o arquivo =(");
}


B recebeu: O dinossauro pulou na lama.
C recebeu: O dinossauro pulou na lama.
D recebeu: O dinossauro pulou na lama.
Gravacao realizada com sucesso!

Exercício

No exercício anterior, o nome dos elementos (tags) e atributos dos tags foi preestabelecido. Considere que você gostaria de usar anotações para configurar o modo como as classes e propriedades são mapeados para XML. Para que seja possível, por exemplo, produzir alternativas como a seguir:

<PERSON name="A">
    <FOLLOWER name="B"/>
    <FOLLOWER name="C"/>
    <FOLLOWER name="D"/>
</PERSON>

Escreva as interfaces de anotação propostas e elabore um exemplo ilustrativo.

Resolução

Foram criadas interfaces para o mapeamento de elementos (tags) e atributos. O mapeamento de seguidores recebe uma anotação com dois campos, para que possa se configurar simultaneamente o elemento e atributo (também seria possível se usar duas anotações).


In [8]:
public @interface MapElemento {
    public String value();
}

public @interface MapAtributo {
    public String value();
}

public @interface MapLigacao {
    public String elemento();
    public String referencia();
}

@MapElemento("PERSON")
public abstract class Pessoa {
    @MapAtributo("name")
    private String nome;
    
    public Pessoa(String nome) {
        this.nome = nome;
    }
   
    public String getNome() {
        return nome;
    }
    
    public abstract void twita(String tweet);
}

@MapElemento("PERSON")
public class PessoaTwitter extends Pessoa implements IObserverSubject {
    @MapLigacao(elemento="FOLLOWER", referencia="name")
    private ArrayList<IObserver> seguidores = new ArrayList<IObserver>();
    
     public PessoaTwitter(String nome) {
         super(nome);
     }
    
     public ArrayList<IObserver> getSeguidores() {
         return seguidores;
     }
    
     public void enviaTweet(String tweet) {
         // faz alguma coisa com o tweet (opcional)
         System.out.println(getNome() + " recebeu: " + tweet);
     }
    
    public void attach(IObserver seguidor) {
        seguidores.add(seguidor);
    }
    
    public void twita(String tweet) {
        for (IObserver segue: seguidores)
            segue.enviaTweet(tweet);
    }

    public void serializa(PrintWriter saida) {
        saida.println("<PESSOA nome=\"" + getNome() + "\">");
        for (IObserver seguidor: seguidores) {
            saida.println("  <SEGUIDOR nome=\"" + seguidor.getNome() + "\"/>");
        }
        saida.println("</PESSOA>");
        for (IObserver seguidor: seguidores)
            seguidor.serializa(saida);
    }
}

Exercício

Escreva dois Factory Methods, o primeiro produz uma Pessoa ou herdeiro de pessoa o segundo produz uma aresta entre duas Pessoas ou herdeiros de Pessoa.

Resolução

Como o método fábrica se propõe a não dar acesso sobre detalhes de implementação ao cliente (por exemplo, qual a classe usada), optou-se por operar sobre a interface IObserverSubject. Note que, ao usar o método fábrica, o cliente deixa de fazer referência a classes e se desacopla da implementação.

Desafio

Como você expandiria para uma fábrica abstrata? Por exemplo, pense em outra linha de produto, e.g., outro tipo de nó e conexão.


In [12]:
public class FabricaPessoas {
    public static IObserverSubject fabricaPessoa(String nome) {
        return new PessoaTwitter(nome);
    }
    
    public static void fabricaConexao(IObserverSubject sujeito, IObserverSubject observador){
        sujeito.attach(observador);
    }
}

// cliente que não faz referência a classes
IObserverSubject observado = FabricaPessoas.fabricaPessoa("Asdrubal");
IObserverSubject observador1 = FabricaPessoas.fabricaPessoa("Doriana");
FabricaPessoas.fabricaConexao(observado, observador1);
IObserverSubject observador2 = FabricaPessoas.fabricaPessoa("Melissa");
FabricaPessoas.fabricaConexao(observado, observador2);
observado.twita("O dinossauro pulou na lama.");


Doriana recebeu: O dinossauro pulou na lama.
Melissa recebeu: O dinossauro pulou na lama.