28 de maio de 2010

Adapter

De acordo com GoF, Adapter serve para "converter a interface de uma classe em outra interface esperada pelos clientes. O Adapter permite que certas classes trabalhe em conjunto, pois de outra forma seria impossível por causa de suas interfaces incompatíveis".

Para exemplificar, vou exibir uma situação simples em que se pode implementar o Adapter. A idéia deste blog é fazer com que você possa entender como funcionam os Padrões, e a partir daí, possa utilizá-lo em sua situação prática.

No nosso exemplo, vamos imaginar que você tenha uma classe responsável por imprimir seus relatórios. O nome desta classe é PrintaRelatorio, e ela vai possuir um atributo chamado out do tipo PrintStream. É um objeto que todos os desenvolvedores Java conhecem bem, utilizado sempre que mostramos algo na tela com System.out.println("texto"); é o mesmo System.out que nossa classe vai utilizar. Ela terá um método chamado imprime que receberá uma String como parâmetro e imprimirá na tela o resultado.

O código completo de PrintaRelatorio.java será:
public class PrintaRelatorio {
private PrintStream out;

public PrintStream getOut(){
return this.out;
}
public void setOut(PrintStream out) {
this.out = out;
}
public void imprime(String texto){
getOut().println(texto);
}
}
Um atributo out do tipo PrintStream, seus getters e setters, e o método imprime() recebendo como parâmetro o texto a ser mostrado no console da aplicação com println(). Bastante simples!

Para utilizar a funcionalidade da classe acima, bastaria fazer em nossa classe Executável:
public static void main(String[] args) {
PrintaRelatorio printa = new PrintaRelatorio();
printa.setOut(System.out);
printa.imprime("texto");
}
O requisito do Cliente era apenas este: ter uma classe que permita imprimir no console uma informação. Está feito!

Entretanto, como todo analista sabe muito bem, um requisito serve para ser alterado pelo Cliente, várias vezes, durante todo o projeto!

Em nosso exemplo, o Cliente quer agora que a informação seja exibida em uma "telinha bonitinha" e não mais no console da linha de comando. Porém, a classe PrintaRelatorio já está homologada em produção, sendo utilizada por vários outros programas, permeada em diversos sistemas... e o fato é que você não vai poder mexer em PrintaRelatorio.java!

E assim surge o nosso primeiro problema: para exibir a informação na tela que o Cliente deseja, precisaremos utilizar JOptionPane.showMessageDialog() no lugar de System.out.println(). Porém, eu não posso fazer printaRelatorio.setOut(JOptionPane) porque este método espera algo do tipo PrintStream. Além do mais, o método imprime() de printaRelatorio invoca o println(), e eu preciso invocar showMessageDialog(). Como resolver isso sem alterar a classe PrintaRelatorio?

Resposta: aplicando o padrão Adapter. De novo, GoF: "converte a interface de uma classe em outra interface esperada pelos clientes". É a mesma situação que temos!

Vou criar uma classe chamada AdaptadoraParaPainel que vai estender PrintStream:

public class AdaptadoraParaPainel extends PrintStream

Ao fazer isso, ela me obriga a inserir um construtor para informar um Arquivo, visto que a especialidade dela é trabalhar com streams. Mas eu não vou trabalhar com arquivos! Então vou inserir o construtor que ela espera, declarar a exceção que ela espera, apenas para o compilador parar de reclamar. Ou seja:

//Construtor
public AdaptadoraParaPainel() throws FileNotFoundException{
.....super(new File("nada"));
}

Aí então vou inserir o método que a classe cliente espera que eu chame, com a mesma assinatura, mas invocando o mecanismo que eu preciso agora, que é showMessageDialog(). Isto é:

public void print(String texto){
.....JOptionPane.showMessageDialog(null,texto);
}

Portanto, assim fica o código completo de AdaptadoraParaPainel.java :
import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import javax.swing.JOptionPane;

public class AdaptadoraParaPainel
extends PrintStream {

public AdaptadoraParaPainel()
throws FileNotFoundException{

super(new File("nada"));
}
public void print(String texto){
JOptionPane.showMessageDialog(null,texto);
}
}
Com isso, minha classe cliente vai poder continuar invocando o método setOut() que espera um PrintStream: a minha classe adaptadora é um PrintStream! O código completo da classe Executavel.java seria:
import java.io.FileNotFoundException;
import java.io.PrintStream;

public class Executavel {
public static void main(String[] args) throws
FileNotFoundException {
PrintaRelatorio printa = new PrintaRelatorio();
printa.setOut(new AdaptadoraParaPainel());
printa.imprime("texto");
}
}
Pronto! A informação impressa em uma tela e não mais no console, como queria o Cliente, e a classe PrintaRelatorio permanece intocada.

Mas eu ainda tenho algo importante a dizer. Na situação apresentada aqui, o padrão Adapter precisou ser utilizado, de fato. Porém, é preciso notar que: um bom projetista, um arquiteto competente, não teria deixado isto acontecer! A forma como foi construída a classe PrintaRelatorio deixou o mecanismo amarrado ao PrintStream! Esta foi uma péssima decisão! Um bom arquiteto teria notado que:

"Peraí! O Cliente está me pedindo um objeto para imprimir no console. Mas vai que amanhã ele mude de idéia e queira imprimir numa tela? Ou numa página html? Ou direto na impressora? Ou enviar por e-mail? Afinal de contas, um requisito do Cliente foi feito para ser alterado, diversas vezes, durante o projeto..."

Um bom arquiteto teria construído sua classe aplicando o Padrão Bridge. Diz o GoF sobre o Bridge: "desacopla uma abstração da sua implementação, de modo que as duas possam variar independentemente". Se o Padrão Bridge tivesse sido aplicado, não teríamos a necessidade de adaptar mais tarde.

O livro do GoF, na página 160, diz que:
"O padrão Adapter é orientado para fazer com que classes não-relacionadas trabalhem em conjunto. Ele é normalmente aplicado a sistemas que já foram projetados. Por outro lado, Bridge é usado em um projeto, desde o início, para permitir que abstrações e implementações possam variar independentemente."

Mas o Padrão Bridge é assunto para outro post. Não deixe de me enviar uma mensagem em caso de críticas, sugestões ou dúvidas. Obrigado e até a próxima.

9 comentários:

  1. Eu entendi, mas no caso do seu exemplo, a classe PrintaRelatório atua de forma indenpendente da AdaotadoraParaPainel...
    Pelo que eu entendi, praticamente você criou uma nova classe para fazer o que queria.

    Isso é adaptar?

    ResponderExcluir
    Respostas
    1. Oi Gabriel, obrigado pela sua participação - desculpe a demora da minha resposta.

      Sim, isto é adaptar! O grande trunfo deste exemplo é que conseguimos mudar o comportamento da classe PrintaRelatorio sem mexer numa única vírgula desta classe!

      Os exemplos aqui apresentados seguem fielmente as soluções apresentadas pelo livro "Padrões de Projeto", da GoF.

      O fato de ter criado outra classe para adaptar me dá uma série de benefícios! Eu poderia criar diversas novas regras, como "AdaptadoraParaEmail", "AdaptadoraParaPdf", etc. Ou seja, seria possível adicionar novos comportamentos SEM dar manutenção na classe original "PrintaRelatorio".

      E é justamente isto que prega o padrão Adapter, da GoF - que é o meu objetivo com este blog: ser fiel aos padrões listados no livro.

      Quaisquer dúvidas, sugestões, ou simplesmente vontade de debater, não deixe de me enviar uma mensagem, ok? :-)

      Estarei à disposição! Grande abraço,
      Marcone

      Excluir
  2. Muito bom! Retirou todas as minhas dúvidas em relação ao Adapter.

    ResponderExcluir
    Respostas
    1. Obrigado, Hymell, espero que você faça bom proveito dos exemplos aqui no blog.

      Grande abraço,
      Marcone

      Excluir
  3. Obrigado a todos vocês, pelos comentários! Estou preparando um novo post sobre o GoF Observer - com muita paciência, farei um post para cada um dos 23 padrões... =D

    Abraços,

    ResponderExcluir
  4. Parabéns pelo exemplo, me esclareceu algumas duvidas, só achei que poderia ter um diagrama de classe para ficar mais claro ainda. Abraço.

    ResponderExcluir
  5. Respostas
    1. Valeu, Jaderson, obrigado pela participação!
      O blog está bem parado, preciso preparar mais um post sobre design patterns. Tem algum que você gostaria logo de ver aqui?

      Excluir