quinta-feira, 16 de outubro de 2008

Entendendo o padrão de projeto Adapter

Então, faz muito tempo que não posto neste blog, mas agora estou com internet de novo em casa, posso tentar atualizar meu blog com mais freqüência. Vou postar outra coisinha simples, mas que muita gente não conhece, relacionado a padrões de projeto (design patterns), um assunto que eu considero o mais, senão um dos mais importantes para alguém que trabalha com orientação a objetos.

Um padrão de projeto é uma solução comum para um determinado problema, existem 23 padrões "clássicos" para programar orientado a objetos, eles são conhecidos como os 23 padrões GoF (Gang of Four devido as 4 pessoas que criaram eles), além desses 23 padrões clássicos existem ainda padrões J2EE (recomendados pela SUN), padrões GRASP (General Responsibility Assignment Software Patterns) entre outros. Qualquer um pode "criar" um padrão, eles surgem de acordo com a evolução das arquiteturas e do desenvolvimento dos sistemas.

Hoje vou falar de um padrão do GoF que eu costumo usar conhecido como Adapter, esse padrão tem como objetivo converter uma interface de uma classe em outra interface esperada pelos clientes, exemplos da utilização pode ser visto na API padrão do java para tratamento de eventos (MouseAdapter, WindowAdapter), Wrappers (Integer, Double), etc. Existem duas formas de Adapter: Class Adapter que utiliza herança múltipla e Object Adapter que utiliza composição. Class Adapter é usado quando o cliente alvo que eu quero adaptar possui uma interface estatica, Object Adapter é usado quando se deseja menor acoplamento ou quando o cliente alvo que quero implementar não usa interface.

Mas vamos aos códigos. Imagine que estamos utilizando um framework de cinema, que possui uma classe para representar filme, uma interface que define ator, e uma classe que representa o ator principal (só para existir uma implementação de ator). Em um filme eu posso incluir vários atores e então rodar o filme:

public interface Ator {

 void representar();

}

public class Filme {

 private List<Ator> atores = new ArrayList<Ator>();

 public void addAtor(Ator ator) {
  atores.add(ator);
 }

 public List<Ator> getAtores() {
  return Collections.unmodifiableList(atores);
 }

 public void rodaFilme() {
  for (Ator ator : atores) {
   ator.representar();
  }
 }
}

public class AtorPrincipal implements Ator {

 public void representar() {
  System.out.println("Ator principal representando");
 }

}

Essas são classes e interfaces do framework de cinema, eu não posso alterar. Agora imagine que eu tenho meu sistema que possui pessoas, e um comportamento dessas pessoas é andar:

public class Pessoa {

 public void andar() {
  System.out.println("Pessoa andando");
 }

}

Com essa minha classe de pessoa, eu queria incluir figurantes no meu filme, pessoas que não são atores, só para ficar andando lá, fazendo número no filme, porém não posso alterar a interface do filme para receber pessoas, o que eu faço?! Adapto pessoa para ser ator, dessa forma:

public class Figurante1 extends Pessoa implements Ator {

 public void representar() {
  andar();
 }

}

Nesse caso eu usei Class Adapter, pois adaptei a classe pessoa a um ator. Porem esse adaptador não serve para qualquer pessoa, ele é uma nova Pessoa, nova classe, ele não adapta pessoas já existentes a um ator, eu tenho que criar realmente um figurante novo, não adaptar uma pessoa, por isso Class Adapter. Caso você precisasse adaptar qualquer pessoa a um figurante, essa não seria a melhor forma, o ideal é que a classe figurante (adaptadora) fosse dessa forma (usando object adapter):

public class Figurante2 implements Ator {

 private Pessoa pessoa;

 public Figurante(Pessoa pessoa) {
  this.pessoa = pessoa;
 }

 public void representar() {
  pessoa.andar();
 }

}

Ambos são adaptadores, qual usar? Depende do que você quer, em minha opinião, o perfeito é dar as duas opções, eu adaptar uma pessoa já existente ou eu criar uma nova pessoa que já é um figurante, para isso pessoa deveria ter uma interface, e ai meu figurante seria dessa forma:

public class Figurante3 implements Ator, InterfaceDePessoa {

 private InterfaceDePessoa pessoa;

 public Figurante(InterfaceDePessoa pessoa) {
  this.pessoa = pessoa;
 }

 public Figurante() {
  this.pessoa = new Pessoa();
 }

 public void representar() {
  pessoa.andar();
 }

 public void andar() {
  pessoa.andar();
 }

}

Isso me permite adaptar qualquer pessoa a um figurante, bem como criar um novo figurante do "zero", ou seja, é uma junção dos dois figurantes apresentados anteriormente. Mas, meu sistema não tem uma interface de Pessoa e eu não quis criar, então vou usar meus dois primeiros figurantes:

public class Main {

 public static void main(String[] args) {
  Ator a1 = new AtorPrincipal();  //um ator de verdade
  Figurante f1 = new Figurante1();  //uma nova pessoa que ja nasceu Figurante
  Pessoa p1 = new Pessoa();  //uma pessoa normal
  Figurante f2 = new Figurante2(p1);  //a pessoa normal adaptada a figurante

  Filme filme = new Filme();
  filme.addAtor(a1);
  filme.addAtor(f1);
  filme.addAtor(f2);

  filme.rodaFilme();
 }

}

Agora vamos evoluir, achei um framework que gerencia tarefas, e eu quero incluir tarefas que ao serem executadas fazem com que todos os atores do filme representem. Esse framework tem uma classe abstrata que representa uma tarefa e um gerente de tarefas:

public abstract class Tarefa {

 public abstract void executar();

}

public class GerenteTarefa {

 public void executar(List<Tarefa> tarefas) {
  for (Tarefa tarefa : tarefas) {
   tarefa.executar();
  }
 }

}

Mas repare que não tenho uma interface de tarefa, então como vou adaptar atores (qualquer ator, até mesmo o figurante) a tarefas?! Solução: Object adapter, ou seja, vou criar meu adaptador a partir de objetos com composição (mesma idéia do figurante2, pois eu quero adaptar qualquer ator a tarefas):

public class AdaptadorDeAtor extends Tarefa {

 private Ator ator;

 public AdaptadorDeAtor(Ator ator) {
  this.ator = ator;
 }

 public void executar() {
  ator.representar();
 }

}

Repare que eu não adaptei a classe, meu adaptador não é um novo ator, ele é uma tarefa que delega seu método “executar” para um objeto de ator, ele adapta qualquer ator a tarefa, executando isso:

public class Main {

 public static void main(String[] args) {
  Ator a1 = new AtorPrincipal();  //um ator de verdade
  Figurante f1 = new Figurante1();  //uma nova pessoa que ja nasceu Figurante
  Pessoa p1 = new Pessoa();  //uma pessoa normal
  Figurante f2 = new Figurante2(p1);  //a pessoa normal adaptada a figurante

  Filme filme = new Filme();
  filme.addAtor(a1);
  filme.addAtor(f1);
  filme.addAtor(f2);

  filme.rodaFilme();

  List<Tarefa> tarefas = new ArrayList<Tarefa>();
  for (Ator ator : filme.getAtores()) {
   tarefas.add(new AdaptadorDeAtor(ator));
  }

  GerenteTarefa gt = new GerenteTarefa();
  gt.executar(tarefas);
 }

}

Ou seja, consegui adaptar minha classe Pessoa para um ator e depois adaptei um ator para tarefa, usando Class e Object Adapter. Entendendo isso, você consegue adaptar qualquer classe para praticamente qualquer interface, talvez muita gente já fez isso, mas não sabia que tinha nome. Para quem nunca usou, pense bem, pode ser muito útil na reutilização de código, integrar sistemas e pode ajudar bastante a melhorar o desenvolvimento.