quinta-feira, 13 de maio de 2010

Visitor sem accept()

O padrão Visitor é deveras útil, mas pode ser melhorado com um pouco de reflexão. O maior problema dele é que é preciso adicionar um método para cada classe a ser visitada. Geralmente o código não faz mais do que chamar o respectivo método visit() no Visitor. Com reflexão, pode-se eliminar esses métodos. Eliminar código é sempre bom!

Vamos começar pelas classes que serão visitadas.

class Circle { 
  public String toString() { 
    return "Circle"; 
  } 
}

class Square { 
  public String toString() { 
    return "Square"; 
  } 
}

class FunnySquare extends Square { 
  public String toString() { 
    return "FunnySquare"; 
  } 
}

class ReallyFunnySquare extends FunnySquare { 
  public String toString() { 
    return "ReallyFunnySquare"; 
  } 
}

Duas são subclasses de Square e para elas não vou criar métodos visit(). O Visitor com reflexão está no código abaixo.

class Visitor {
  
  private void visit(Circle c) { 
    System.out.printf( "I visited a Circle (%s)\n", c); 
  }
  
  private void visit(Square s) { 
    System.out.printf("I visited a Square (%s)\n", s);
  }

  public void visit(Object e) throws Exception {
    visit(e, e.getClass());
  }
   
  private void visit(Object e, Class c) throws Exception {
    try {
      Method m = getClass().getDeclaredMethod("visit", c);
      m.invoke(this, e);
    } catch(NoSuchMethodException nsme) {
      visit(e, c.getSuperclass());
    } catch(Exception ex) {
      throw ex;
    }
  }
}

O único método público é o visit(Object e). Ele usa uma versão privada que procura um método adequado para a classe do alvo e, se não achar, recursivamente procura métodos usando a superclasse do alvo.

O código abaixo executa um pequeno teste.

Circle c = new Circle();
Square s = new Square();
FunnySquare f = new FunnySquare();
ReallyFunnySquare r = new ReallyFunnySquare();

Visitor v=new Visitor();
for(Object o : new Object[] {
        new Circle(), new Square(), new FunnySquare(), new ReallyFunnySquare()
  }) {
    v.visit(o);
  }

E o resultado é:

I visited a Circle (Circle)
I visited a Square (Square)
I visited a Square (FunnySquare)
I visited a Square (ReallyFunnySquare)

Eu deixei apenas um método visit() público para evitar que o cliente use um método mais abrangente. Como o compilador escolhe o método conforme o tipo da referência e não o tipo do objeto, se for utilizada uma referência com tipo de uma superclasse da instância, perde-se a oportunidade de usar um método mais específico.

Um bônus nisso tudo é que não é preciso usar ifs.

Nenhum comentário: