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);
}
Algumas decisões importantes:
tweet
, portanto a solução dessa parte é aberta (decidi imprimir no console).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.");
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>
Usando Pipe&Filter
escreva um método que receba como parâmetro um objeto da classe Pessoa
ou herdeira e a serialize.
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);
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 =(");
}
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>
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 =(");
}
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.
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);
}
}
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
.
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.
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.");