É muito comum utilizarmos objetos diferentes que fazem mesma coisa, no entanto, de modo diferenciado… Confuso?!?!
Muito bem… Imagine que você esteja desenvolvendo um sistema que pode emitir boleto de vários bancos diferentes. Para todos os bancos, os boletos possuem características semelhantes como: código de barras, número de documento, nosso número, código do banco entre outros. Ou seja, todo e qualquer boleto quando chamado deve retornar esses dados, mas cada banco tem uma regra própria para realizar os cálculos e retornar, por exemplo, os dados do código de barras.
Inicialmente podemos ter dois bancos para começarmos o sistema que, de cara, poderá ter mais bancos muito em breve. Vamos começar, por exemplo, com Itaú e Banco do Brasil (não, não é merchan…rsrs).
Claro, prevendo o crescimento, os bancos estão configurados em banco de dados ou o nome é passado na URL. De qualquer forma o nome do banco escolhido estará em uma string. Supondo que as classes já estão prontas com seus respectivos métodos, isso é muito tranquilo com design patterns como “factory” ou “builder” com um método que retorna o tal banco.
class FabricaBanco
def banco_atual(nome)
case nome
when "itau"
Itau.new
when "banco_do_brasil"
BancoDoBrasil.new
end
end
end
Na classe BancoDoBrasil, por exemplo, termos o método que retorna o valor do código de barras:
class BancoDoBrasil
def codigo_barras()
"12345678901234567890123456789012345678901234"
end
end
É só fazer a chamada em seu controller e usar:
banco = banco_atual "banco_do_brasil" codigo_barras = banco.codigo_barras
Agora o sistema começou a crescer e seus clientes estão pedindo boleto do Bradesco também. É só adicionar mais um banco na condição:
class Banco
def banco_atual(nome)
case nome
when "itau"
Itau.new
when "banco_do_brasil"
BancoDoBrasil.new
when "bradesco"
Bradesco.new
end
end
end
Até aí tudo bem… Mas, cada vez que surgir um novo banco, além de criar sua classe, será necessário adicionar mais um banco à condição. Naturalmente vamos criar um método gigante e aumentar a complexidade de manutenção.
Não seria mais fácil se aproveitássemos o nome do banco da string? Ruby tem algumas funções “mágicas” muito úteis (claro, não existe mágica em Ruby, mas é só para ilustrar), no entanto deve-se tomar muito cuidado! Já explico o porquê.
Nesse caso podemos usar o método “eval” para converter a string em um objeto conhecido pela aplicação e resumir nosso método “banco_atual” com apenas uma linha de implementação. Mas, além disso, é necessário que o nome do banco também seja semelhante ao da classe. Usaremos o método “camelize” do Ruby on Rails que converte a string que está em lower case para camel case. Ex.: de “banco_do_brasil” para “BancoDoBrasil”. E nosso código fica assim:
def banco_atual(nome) eval nome.camelize.new end
Agora vem o alerta!!! O método “eval” transforma qualquer string em código Ruby. Por exemplo, no “irb” digite:
>> eval “puts ‘QUE PERIGO!’”
Vai ser exibido “QUE PERIGO!” em seu terminal, ou seja, foi executado o que estava na string passada para o método “eval”. Isso significa que, para utilizar esse método você deve estar muito consciente do que está fazendo e, principalmente, deve “sanitizar” seu código para proteger sua aplicação.
Uma boa saída é utilizar o método “constantize” do Ruby on Rails que tenta encontrar uma constante ou objeto com o nome da string em camel case. Caso não encontre ou a string não esteja em camel case, é devolvido um NameError.
def banco_atual(nome) nome.camelize.constantize.new end
Mesmo assim, não deixe de tratar os inputs!