segunda-feira, 14 de janeiro de 2008

Repassando parâmetros rest para funções que esperam parâmetros rest

No ActionScript 3 existe um parâmetro especial, comumente chamando de parâmetro rest, que permite que uma função receba "infinitos" parâmetros. O Java, apartir da versão 5, também possui essa facilidade, que é chamada de varargs. Veja abaixo o exemplo de uma função e o logo abaixo o uso dela:
//Funcao que mostra todos os parametros
public function mostra(... numeros) {
if( numeros && numeros.length > 0 ) {
for each( var i in numeros ) {
trace(i);
}
}
}
//Uso da funcao:
mostra(1);
mostra(1,2,3);
mostra(1,2,3,5,8);
Apesar de ser um recurso muito bom, a forma como ela foi implementada no ActionScript nos traz um problema no momento em que precisamos repassar esses parâmetros para uma outra função que também receba parâmetros opcionais rest.

Isso acontece porque quando a primeira função é chamada, ela é informada com os parâmetros separados por vírgula, mas ela os recebe como um array. Então se você enviar este array para o segundo método, ele será considerado apenas um parâmetro. No Java há um tratamento mais esperto, de forma que quando enviado um array o compilador não permite que mais nenhum parâmetro seja colocado na chamada à função.

Para ilustrar melhor vamos pensar num cenário que mostrei no post passado sobre como testar funções que executem javascript. No caso era uma função proxy para isolarmos a execução de ExternalInterface.call:
public function executarJavaScript(funcao:String, ... parametros) {
try {
return ExternalInterface.call(funcao,parametros);
} catch(erro:Error) {
return null;
}
}
No caso, a função call de ExternalInterface também espera receber parâmetros opcionais, mas como estamos mandando um array, ele achará que estamos mandando apenas dois parâmetros, um com o nome da função javascript e outro com um array. No Google é fácil encontrar uma solução POG e muito feia para isso. Nada mais é que uma cadeia megazord de ifs:
public function executarJavaScript(funcao:String, ... p) {
try {
if( p.length == 0 ) return ExternalInterface.call(funcao,null);
else if( p.length == 1 ) return ExternalInterface.call(funcao,p[0]);
else if( p.length == 2 ) return ExternalInterface.call(funcao,p[0],p[1]);
else if( p.length == 3 ) return ExternalInterface.call(funcao,p[0],p[1],p[2]);
else if( p.length == 4 ) return ExternalInterface.call(funcao,p[0],p[1],p[2],p[3]);
} catch(erro:Error) {
return null;
}
}
Ô coisa feia!

A solução mais elegante para esse problema é utilizar os recurso de reflexão do ActionScript. Ou seja, vamos utilizar o método apply da classe Function, que aguarda os parâmetros como um array, exatamente como recebemos na função ao utilizar um parâmetro rest. Veja como ficaria:
public function executarJavaScript(funcao:String, ... parametros) {
try {
var f:Function = ExternalInterface.call;
var parametrosEnviar:Array = new Array(funcao);
parametrosEnviar = parametrosEnviar.concat(parametros);
return f.apply(null,parametrosEnviar);
} catch(erro:Error) {
return null;
}
}
No caso, concatenamos num array de parâmetros a serem enviados a função e os parâmetros rest. Ao enviá-los pela função apply eles são enviados como se fossem diversos parâmetros separados por vírgula e o método call os recebe corretamente como parâmetros rest.

Nenhum comentário:

Postar um comentário