Visitor e Double Dispatch
Vamos dar uma olhada na seguinte hierarquia de classe para formas geométricas (cuidado com o pseudocódigo):
O código funciona bem e a aplicação está em produção. Mas um dia você decide criar uma funcionalidade de exportação. O código da exportação seria alienígena se colocado nessas classes. Então ao invés de acionar a exportação para todas as classes dessa hierarquia você decidiu criar uma nova classe, externa à hierarquia e colocar toda a lógica de exportação lá dentro. A classe obteria métodos para a exportação do estado público de cada objeto em strings XML:
O código parece bom, mas vamos testá-lo:
Espera aí! Por quê?!
Pensando como um compilador
Nota: a seguinte informação é verdadeira para a maioria das linguagens de programação modernas orientadas aos objetos (Java, C#, PHP, e outras).
Vinculação tardia/dinâmica
Finja que você é um compilador. Você tem que decidir como compilar o seguinte código:
Vejamos... o método draw
definido na classe Shape
. Espera um segundo, mas também há quatro subclasses que sobrescrevem esse método. Podemos decidir com segurança qual das implementações chamar aqui? Parece que não. A única maneira de saber com certeza é rodar o programa e checar a classe de um objeto passado para o método. A única coisa que sabemos de certeza é que o objeto terá a implementação do método draw
.
Então o código máquina resultante irá checar a classe pelo parâmetro s
e pegar a implementação draw
da classe apropriada.
Esse tipo de checagem dinâmica é chamada de vinculação tardia (ou dinâmica):
- Tardia, porque nós ligamos o objeto e sua implementação após a compilação no tempo de execução.
- Dinâmica, porque cada novo objeto pode precisar estar ligado à uma implementação diferente.
Vinculação antecipada/estática
Agora, vamos “compilar” o seguinte código:
Tudo fica claro com a segunda linha: a classe Exporter
não tem um construtor, então nós apenas instanciamos um objeto. E a chamada para o export
? A Exporter
tem cinco métodos com o mesmo nome que diferem por tipos de parâmetro. Qual delas chamar? Parece que nós vamos precisar de uma vinculação dinâmica aqui também.
Mas há outro problema. E se houver uma classe de “forma” que não tem o método export
apropriado na classe Exporter
? Por exemplo, um objeto Ellipse
. O compilador não pode garantir que o método de sobrecarregamento adequado exista em contraste com os métodos sobrescritos. Uma situação ambígua aparece que o compilador não pode permitir.
Portanto, desenvolvedores de compiladores usam um caminho seguro e usam a vinculação antecipada (ou estática) para métodos sobrecarregados:
- Antecipada porque acontece durante o tempo de compilação, antes do programa ser rodado.
- Estática porque não pode ser alterada no tempo de execução.
Vamos voltar ao nosso exemplo. Nós temos certeza que o argumento que está vindo será da hierarquia Shape
: seja da classe Shape
ou uma de suas subclasses. Nós também sabemos que a classe Exporter
tem uma implementação básica da exportação que suporta a classe Shape
: export(s: Shape)
.
Essa é a única implementação que pode ser ligada de forma segura a um código sem tornar as coisas ambíguas. É por isso que mesmo que passamos um objeto Rectangle
em exportShape
, o exportador ainda vai chamar um método export(s: Shape)
.
Double Dispatch (Despacho Duplo)
O double dispatch é um truque que permite usar a vinculação dinâmica junto com método sobrecarregados. Veja como é feito:
Posfácio
Mesmo que o padrão Visitor seja construído no princípio do double dispatch (despacho duplo), esse não é seu propósito principal. O Visitor permite que você adicione operações “externas” para toda uma hierarquia de classe sem mudar o código existente dessas classes.