Overload em Javascript

E ae gente, é nóis de novo =).

Eu queria falar desta vez sobre a técnica conhecida como overloading ou sobrecarga de métodos mas em javascript. Sabemos que a linguagem até a versão 1.5 não oferece isso por padrão mas oferece uma série de coisas que nos ajudam a contornar essa falta.

O que seria uma sobrecarga ou um overloading? Em Ciência da Computação, overloading é uma “feature” da linguagem que nos permite criar uma série de métodos de mesmo nome mas que se diferem uma das outras pelo número de parâmetros, pelo tipo dos parâmetros ou pelo seu retorno, ou os três ao mesmo tempo.

Se você quisesse fazer um método setter e getter, você estaria extremamente inclinado à fazer isso:

string getNome(){ return nome; }
void setNome(string n){ nome = n }

Não há nada errado no código acima, mas não estaríamos usando as técnicas de programação OO se fizessemos isso. Um problema que vejo nessa implementação é que sua aplicação tende a ficar com muitos métodos de nomes diferentes, aumentando consideravelmente a curva de aprendizado de um programador terceiro que pretende usar esse código. Ao invés de ter que aprender 10 nomes de métodos, o programador teria de aprender 20, utilizando como base o esquema feito acima.

Uma maneira elegante então seria algo do tipo:

string nome(){ return Nome; }
void nome(string n){ Nome = n; }

Desta forma você só precisaria conhecer o método “nome”. Se você não passar nada ele te retorna o nome. Se você passar alguma coisa, significa que você está querendo “setar” o nome.

Em C++ e Java é possível fazer o que fiz acima sem maiores preocupações. A própria linguagem oferece esse recurso para que possa sobrecarregar os métodos.

Mas e em Javascript ?

Eu já vi várias e excelentes implementações de sobrecarga em vários blogs brasileiros e gringos, mas o que me incomodava nessas implementações é que sempre usavam algum iterador para resolver o problema. Sempre tinha um “for” ali ou algo do tipo. Seguindo a mesma filosofia dos outros posts, pretendo usar um método de sobrecarga que utilize os próprios recursos da linguagem, assim como fiz com Herança e Classes em um post mais antigo.

Como fazer?

Sabemos que o compilador ou interpretador precisa diferenciar os métodos de mesmo nome de duas formas. Primeiro a quantidade de parâmetros que passamos para a função. A segunda é pelo tipo de parâmetro que passamos.

Eu escolhi neste método usar um ou outro, não misturar os dois ao mesmo tempo porque até agora nunca precisei usar este tipo de sobrecarga em Javascript, então é código escrito sem necessidade. Então mãos a massa.

Sobrecarga – Número de parâmetros

Mais uma vez vou utilizar o método “apply” do Js para resolver essa questão, como a linguagem mexe muito com prototipos e construtores, nada mais inteligente do que usar esse método de escopo. A idéia que tive foi usar uma função como uma função e ao mesmo tempo como um objeto que segura suas próprias propriedades.

var Person = {
	Class: function(){	
		//Private
		var name = ''
		
		//Public
		this.name = function(){
			return this.name[ arguments.length ].apply( this, arguments )
		}
		
		this.name[0] = function(){ return name }
		this.name[1] = function(n){ name = n }
		this.name[2] = function(a, b){ name = a + " " + b }
	}	
}

Aqui eu crio uma função ( método ) com nome “name” e crio mais 3 propriedades dela que são: 0, 1 e 2. São números, mas ainda sim são propriedades do método name. O que a função principal vai fazer é ver quantos parâmetros ela tem e vai chamar a sua propriedade correspondente, que nada mais é que outro método que vai ser executado no mesmo contexto que a name e terá os mesmos parâmetros que a name.

Logo:

var Edu  = new Person.Class

Edu .name() //  ''
Edu .name('Eduardo') 
Edu . name() // "Eduardo"
Edu . name('Eduardo', 'Ottaviani')
Edu . name() // "Eduardo Ottaviani"

Não é porque eu tive essa idéia não, mas eu considero esta a forma mais elegante de sobrecarga de operadores até agora. Talvez não seja a mais eficiente, não testei sua velocidade em relação as outras técnicas. Se for mais lenta, creio que não seja perceptível.

Mas e na hora de sobrecarregar a função através do tipo do parâmetro passado? Como fazer?

Sobrecarga – Tipo de parâmetro

Bom, os métodos existentes até agora consistem em checar o objeto através do typeof, então faz-se um switch onde caso o objeto seja uma string faz uma coisa, se for número faz outra etc.

Há duas formas de saber o que é um objeto, uma é usar o typeof que considero ruim.

Ruim porque ela não retorna exatamente o que deseja. Um caso clássico é dar um typeof []. Retornaria um “object”. Um array é um objeto mas não é apenas um objeto, ele é um objeto do tipo Array.

A outra forma é olhar seu construtor. Esta sim é uma excelente forma de se saber qual o tipo de um objeto. Por que? Porque Javascript define os construtores como as “Classes” dos objetos, portanto uma string é definida por um construtor String, um número é definido por um construtor Number e assim por diante. Em javascript, é o construtor que define o tipo do objeto.

Quando você entende isso, você entende tudo sobre a mecânica da linguagem Javascript, e percebe que há muito mais tipos na linguagem do que pensava.

Por exemplo: (Ruim)


typeof new Date() // object
typeof new Person.Class // object
typeof [] // object

Exemplo : ( Bom )

new Date().constructor == Date // true
var edu = new Person.Class; edu.constructor == Person.Class // true
[].constructor == Array // true

Viram? é o construtor que define o tipo do objeto, então mesmo o Person.Class é um tipo, e pode ser definido como o tipo “Person.Class” e não um object.

Dada essa explanação sobre os tipos, como eu faria para implementar um overload de tipos ?

Desta forma:

var Person = {
	Class: function(){	
		this.type = function(){
			return this.type[ 
				arguments[0].constructor 
			].apply(this, arguments)
		}
		
		this.type[String] = function(){
			alert('String')
		}
		
		this.type[RegExp] = function(){
			alert('Regex')
		}
		
		this.type[Person.Class] = function(){
			alert("Person")
		}			
	}	
}

A lógica aqui é a mesma da lógica de sobrecarga por quantidade de argumento, só que desta vez eu armazeno no método type outros métodos como propriedades e crio uma relação de tipo entre eles.

Ao executar o método type() passando um objeto como parâmetro, essa função tentará chamar uma propriedade com o valor de construtor igual ao do objeto do parâmetro. Achando esta propriedade, ela será executada no mesmo contexto e usando os mesmos argumentos que a função type mãe.

O interessante é que você acaba descobrindo que há muito mais tipos de objetos no Js, porque usando o typeof você não consegue diferenciar todos eles.

Olha só:

var Edu = new Person.Class

Edu . type ("Eduardo") // alert  - "String"
Edu . type (/Eduardo/) // alert - "Regexp"
Edu . type (Edu)         // alert - "Person"

Eu poderia também sobrecarregar para um outro método que reconheça uma Date(), usando o mesmo princípio dos outros métodos.

É possível ainda chamar os métodos de tipo ou número diretamente, sem precisar chamar o pai:


Edu . type[RegExp]()
Edu . name[2](a, b)

Sinceramente eu prefiro mil vezes usar os recursos da própria linguagem à usar uma função a parte que itere sobre os objetos, faça comparações ou coisas do tipo. Eu estudei alguns tipos de sobrecarga de blogs gringos, caso vocês não tenham gostado da minha forma, eu recomendo usar esses outros métodos de sobrecarga:

John Resig (jQuery) : http://ejohn.org/blog/javascript-method-overloading/
http://webreflection.blogspot.com/2010/02/javascript-overload-patterns.html

É isso galera. Até o próximo post. Comentários são muito bem vindos.

Aqueleabrassss

Anúncios