Archive for category Design Pattern
OOP em Javascript – Classes Abstratas
Publicado por javiani em Boas práticas, Design Pattern, Javascript, OOP em junho 19, 2011
E aí galera!
Tenho andado bastante atarefado no trabalho, o que contribui para minha falta aqui no blog, estou muito mais no momento de resolver problemas rapidamente do que de refletir, tenho sentido falta disso.
Mas para não deixar o blog tão abandonado resolvi retomar os posts sobre orientação à objetos em Javascript, porque eu acho muito interessante a forma como resolvemos os problemas nessa linguagem.
Então, vamos lá. O que seria uma Classe abstrata no nosso desenvolvimento? A grosso modo é uma classe com propriedades e/ou métodos como qualquer outra, porém é uma classe que não faz sentido existir por si própria, ou seja, não faz sentido haver uma instância desta classe.
Essa classe serve apenas como base para outras classes que irão herdar seu comportamento, suas características.
Um exemplo, se tivessemos que criar um carro, sabemos que este tem o comportamento de se deslocar, um carro acelera, desacelera e consequentemente altera sua velocidade. Então todo Carro que se preze tem uma velocidade, pode acelerar, pode desacelerar até o momento em que sua velocidade for zero.
Então poderiamos criar esta classe Carro:
var Carro = {
Class :function(){
//Private
var velocidade = 0
//Public
this.acelerar = function(n){
velocidade = velocidade + (n || 1)
}
this.frear = function(n){
velocidade = velocidade - (n || 1)
}
this.velocidade = function(){ return velocidade }
}
}
Esta classe vai funcionar direitinho como planejamos, vai frear, vai acelerar e tem uma propriedade velocidade, privada, que será alterada a cada ação que fizemos.
Porém na perspectiva do paradigma OO a forma como criamos a classe Carro está equivocada. Ela está engessada com métodos e propriedades que não poderiam ser reaproveitados em outras classes de mesmo propósito. Ou melhor poderíam, mas de forma completamente errada.
Veja, se quiséssemos criar uma Moto, com esta classe Carro já implementada, haveria duas formas erradas de se criá-la.
Uma é duplicar a classe Carro e chamar de Moto. ( Se você fizer isso você não vai pro céu )
Outra seria herdar de Carro as propriedades e métodos. ( Se você fizer isso você não só não vai pro céu como vai direto pro inferno )
Duplicar está fora de cogitação, orientação à objetos serve justamente para reaproveitarmos código.
Herdar de Carro é um erro de arquitetura sério, como pode uma Moto ser um Carro ? Não pode.
O jeito de resolver esse problema é criar uma classe base, que seja comum entre os dois objetos e estes dois objetos devem herdar desta classe.
A solução é criar uma classe chamada Veiculo. Bom, um carro é um veículo e uma moto é um veículo, ambos tem o comportamento de um veículo que é de se movimentar, acelerar, frear, alterar sua velocidade.
Beleza, então essa classe Veiculo já resolveria nosso problema, mas esta classe não é uma classe como a Moto ou o Carro. Ela é uma classe que serve apenas para ser herdada, não faz sentido existir uma instância dela, não existe um veículo por si só, veículo é uma designação que damos para um determinado objeto, no nosso caso um carro ou uma moto. Você pode dizer que vai pegar um veículo, ou vai guiar um veículo, mas este veículo no nosso mini-mundo ou é um Carro ou é uma Moto.
Então a classe Veiculo é uma classe abstrata.
Em linguagens mais fortemente tipadas como C++, Java, C# é possível através da própria linguagem definir que uma classe não deve existir por si só, não deve existir uma instância dela e que ela deve ser apenas herdada. No Javascript não é possível definir se uma classe é abstrata apenas usando uma palavra-chave, então, para arquitetar uma solução de classes onde usamos classes abstratas e queremos disciplinar outros programadores para que usem nosso código corretamente, precisamos usar um outro tipo de técnica.
Existem várias técnicas na internet, e essa é a vantagem do Javascript dada sua flexibilidade. Vou mostrar a forma como eu faço, e que eu prefiro fazer. Isto fica aqui entre nós, desenvolvedores de Interface, programadores Javascript, porque se aparecer qualquer programador back-end, desktop, engenheiro de software, vai querer nos matar, porque vamos ferir o conceito de Interfaces, novamente.
Falei em um post passado, sobre como usar Interfaces em Javascript, mas uma interface que é mais um mixin do que uma interface. Mas para nós, programadores Javascript, é uma Interface mais malemolente. Lembrem-se de que uma Interface não deve especificar como se implementa, apenas o que se implementa, porém no Javascript ela vai dizer também como se implementa. Se não viu o post sobre interfaces, eu aconselho que veja para entender o que estou falando. =)
Tá, mas por que estou falando tanto de Interfaces se nosso problema é como forçar que uma classe seja abstrata ?
Porque vamos resolver esse problema justamente usando uma “Interface“.
Como ? Olha só:
var Interface = {
abstract :function(c){
if( c.prototype.isPrototypeOf(this) )
throw new Error('Abstract Class')
}
}
Criei ali uma Interface que possui uma implementação para um tipo abstrato de classe. Existe um método abstract que vai receber um construtor e vai dizer se a classe é abstrata, perguntando se o protótipo do escopo atual é o mesmo protótipo do construtor. Em outras palavras, se somos uma instância daquele construtor então lançamos um erro.
Então, para criarmos todos os nossos veículos usaríamos a Interface para criarmos a classe abstrata Veiculo e herdaríamos Veiculo em Carro e Moto:
var Veiculo = {
Class :function(velocidade){
Interface.abstract.apply(this, [Veiculo.Class])
this.acelerar = function(n){
velocidade = velocidade + (n || 1)
}
this.frear = function(n){
velocidade = velocidade - (n || 1)
}
this.velocidade = function(){ return velocidade }
}
}
var Carro = {
Class :function(){
Veiculo.Class.apply(this, [0])
// Implementações de um carro
}
}
var Moto = {
Class :function(){
Veiculo.Class.apply(this, [0])
// Implementações de uma moto
}
}
var carro = new Carro.Class
carro.acelerar(20)
carro.velocidade() // 20
var moto = new Moto.Class
moto.acelerar()
moto.velocidade() // 1
**Perceba que eu passei Veiculo.Class para a Interface.abstract além de passar 0 como a velocidade inicial.
É claro que aqui estamos com duas classes idênticas Moto e Carro, porém suas classes seriam preenchidas com métodos e propriedades específicas dos seus tipos posteriormente.
É isso, os puritanos e estudiosos do paradigma orientado à objetos tentariam mandar me matar ao ver que usei Interface para criar uma classe abstrata, mas fica aqui entre nós
.
Aproveite e tente rodar na sua máquina estas classes, veja o que acontece quando se tenta criar uma instância de Veiculo. =)
Um grande abraço.
O Mvc e o Javascript – Na prática
Publicado por javiani em Design Pattern, Idéias, Javascript, New school em dezembro 30, 2010
Fala galera,
Eu tinha prometido uma implementação do Mvc na prática, e resolvi fazer isso através de um screencast, porque era mais fácil de explicar, mais fácil de demonstrar…
Fiz uma aplicação simples usando a API do Flickr, e fiz já considerando que conhecem o esquema Mvc que comentei no post anterior, para não perder muito tempo com explicações.
Ainda quero fazer um post mostrando a aplicação da galeria de imagens, mas vou precisar de um pouco mais de tempo para formulá-lo.
Perdoem a voz de ganso morto, mas é a voz que Deus me deu. =)
Qualquer dúvida, ou crítica, postem aqui. =P
Abraço.
Screencast : O Mvc e o Javascript – Na prática
Aprox. 15 min
Paginação em Javascript – Parte 2 Final
Publicado por javiani em Boas práticas, Design Pattern, Javascript, New school, Técnicas de programação em setembro 5, 2010
Olá galera, esse post é a segunda parte sobre paginação em Javascript, dessa vez vou mostrar a segunda forma que havia comentado no post anterior sobre como fazer a paginação.
Na forma anterior, lembram, a paginação tinha como função pegar os elementos e manipulá-los por conta própria, sendo que você como desenvolvedor bastaria passar alguns parâmetros para a classe, ela se encarregaria de fazer tudo sozinha.
É claro que não é a melhor classe do mundo, nem a mais eficiente, para pequenos propósitos ela funciona muito bem, mas não é um bom código por exemplo para um grande número de páginas. Não sei se perceberam, mas o script toda hora, ou melhor, cada vez que é clicado em uma nova página, ele remove todos os nodos das páginas, e remove também todos os nodos da página atual e depois reescreve-os, criando tudo de novo e fazendo o append no documento.
Isso é extremamente dispendioso, extremamente lento no que diz respeito ao dom. Vocês vão perceber isso se passarem um “size” de tamanho muito grande, gerando muitas páginas. Ao clicar numa página que não a atual, vão perceber que o script demora muito tempo pra ser executado, porque precisa apagar todos aqueles nodos e gerá-los de novo. O DOM é muito lento, e da forma como é feita na jsPagination, o torna mais lento ainda.
Há ainda um outro problema, que tenho percebido de uns tempos pra cá no desenvolvimento dos meus scripts. Existe um erro grande ali no design do script. Se é um script que faz paginação, ele só deveria fazer a lógica da paginação. O que você vai fazer com as páginas e como vai montá-las é um outro problema.
A medida que o script cresce ele se torna mais complexo e quanto mais complexo ele fica, mais difícil de dar manutenção e maiores são as chances de surgirem bugs, é inevitável.
Então comecei a “atomizar” mais minhas “classes” fazendo com que elas apenas resolvam o problema que elas precisam resolver e deixar o resto na mão de outra classe. Menos é mais, quanto mais modularizado o código, melhor.
Neste post vou resolver apenas o segundo problema da classe, ela ainda vai adicionar e remover os elementos como fazia anterioremente, mas agora ela fará parte de uma outra classe de implementação, podendo ser melhorada ou estendida independentemente da classe de paginação em questão.
Então a classe atomizada, que apenas contém a lógica da paginação ficou assim:
;(function(namespace){
var Interface = {
actions : function(){
this . pages = function(){
throw new Error('Implemente o método pages em sua classe/objeto')
}
this . list = function(){
throw new Error('Implemente o método list em sua classe/objeto')
}
}
}
var Paginator = {
Class : function( target ){
var json
Interface.actions.apply( this )
this.ready = null
this.open = function(options){
if(!json) json = options
else for(var i in options) json[i] = options[i]
var pages =
Math.floor( json.size / json.show ) +
( ( json.size % json.show ) ? 1 : 0 )
json.page = json.page ? json.page : 1
this.begin? this.begin() :null
for( var i = 1; i <= pages; i++ )
this.list.call? this.list.call(this, i, json) :null
for(
var i = ( (json.page-1 ) * json.show );
i< ( ( json.page-1 ) * json.show ) + 1 + ( json.show - 1 );
i++
){
if( i > json.size - 1 )
break
this.pages.call? this.pages.call(this, i, json) :null
}
this.ready? this.ready() :null
}
}
}
namespace['Paginator'] = Paginator.Class
})(window)
Então essa classe só faria chamadas aos métodos que implementam a população de elementos html. O método open ao ser chamado configuraria as opções passadas, e chamaria os métodos públicos :
this.begin() : Método que é chamado logo antes dos outros métodos da paginação.
this.list() : Método que será chamado n vezes, sendo n o número de páginas.
this.pages() :Método que é chamado n vezes, sendo n o número de elementos à serem mostrados.
this.ready(): Quando tudo estiver sido chamado, ou seja, o ready é um callback.
Os métodos list e pages são chamados no escopo da classe de paginação e com dois argumentos, o primeiro um inteiro contendo o índice da paginação e o segundo contendo as opções passadas na chamada do método open, mantendo as opções privadas.
Então agora, como e onde a paginação vai colocar os elementos, não é mais o cargo do Paginator, é cargo de alguma outra classe que vai herdar Paginator ou compor a mesma.
A classe estando atomizada, ela fica bem mais fácil de ser entendida, mais fácil de dar manutenção, a lógica não fica misturada e código menos emaranhado.
A otimização, implementação de plugins e a forma como serão adicionados os elementos fica a cargo de uma outra classe como eu havia comentado anteriormente, através de herança ou composição.
Eu fiz um código para provar essa parte toda teórica, preferi usar composição para reproduzir o funcionamento da classe antiga que postei na primeira parte do post. Fiz um sigleton que será responsável por instanciar o Paginator, e fazer todo o trabalho com os nodos de inserção e remoção. É claro que o código ficou bem menor do que o antigo código do jsPaginator, mostrado no post anterior, isso não se deve apenas pela atomização da classe, mas porque eu não implementei os plugins que haviam na classe antiga e porque agora estou usando jQuery =), não tenho tanto tempo livre como tinha antigamente então preciso ser mais produtivo, caso contrário postaria um conteúdo novo a cada 5 meses =/.
Então, para finalizar o post e deixá-los em paz, aqui vai o singleton que havia comentado:
;(function(){
var Paginate = {
pages :null,
content :null,
temp_pages :null,
instance :null,
callback :null,
initialize : function( paginacao, content ){
this.pages = $(paginacao)
this.content = $(content)
this.temp_pages = this.pages.eq(0).clone()
this.start()
return this
},
start : function(){
var self = this
var paginator = new Paginator
this.instance = paginator
paginator . begin = function(){
self.content.empty()
self.temp_pages.empty()
}
paginator . pages = function(i, json){
self.content.append( $('<p />').html(i+1) )
}
paginator . list = function(i, json){
var self_paginator = this
var el = $('<a />').attr('href', '#').html(i)
.click(function(){
json.page = i
self_paginator.open()
})
if( json.page == i )
el.addClass('selected')
self.temp_pages.append( $('<li />').append(el) )
}
paginator . ready = function(){
self.pages.each(function(){
$(this)
.empty()
.append( self.temp_pages.children().clone( true ) )
})
self.callback? self.callback(self) :null
}
},
open : function(options){
this.callback = options.callback? options.callback :null
this.instance.open(options)
}
}
// Fim do Singleton
Paginate
.initialize('.paginacao', '#pagina')
.open({
size : 50,
show : 10,
callback : function(){}
})
})()
Eu acho que o Singleton Paginate está bem simples, não vou perder muito tempo explicando como ele funciona, acho que dá pra entender. Se alguém tiver alguma dúvida ou não entendeu alguma parte, poste no comentário, que eu explico =). Ah, críticas também são muito bem vindas.
Abraço galera, obrigado pela atenção e até o próximo post. o/
Design Pattern Observer em Javascript
Publicado por javiani em Design Pattern, Javascript, OOP, Técnicas de programação em maio 22, 2010
E aí galerinha do mal, é nóis again.
É engraçado que sempre ao se começar um post eu fico horas só pensando na saudação rsrss. É difícil inovar.. mas ainda que seja repetitivo é melhor que aquele “Bom dia” que alguns colocam né ? Caray, quem garante que a criatura que vai ler, estará lendo de manhã ? =/
Deixando de lado as viadagens, vamos ao que nos interessa de verdade que é Javascript na veia irmão.
Saber sobre os Design Patterns e onde aplicar é o que diferencia os programadores homens dos programadores meninos. Às vezes não é tão trivial identificar qual design pattern usar para um determinado problema e não é possível saber todos decor.
E é por isso que estou postando bastante sobre esse assunto, para termos uma referência na nossa linguagem sobre isso.
Então, o que é um Observer e quando usar ?
Tá de pé? Então senta.
O padrão Observer é um padrão do tipo comportamental, no qual um objeto chamado “Subject” contém uma lista de dependentes, chamados Observers. Os Subjects então notificam para esta lista de Observers automaticamente quando algum estado modifica, geralmente chamando um de seus métodos.
É geralmente usado para implementar sistemas distribuidos com delegação de eventos.
Fonte: Wikipédia
Este padrão usa alguns conceitos de outros design patterns, como Interfaces e Visitor. Lembram que eu falei pra estudar bastante sobre Interfaces pois todos os outros design patterns utilizam este conceito ? Este padrão utiliza interfaces e utiliza também uma idéia do padrão Visitor que se chama inversão de métodos. O Visitor não é pré-requisito para entender o Observer, mas é interessante saber que muitos design patterns utilizam conceitos de outros para se compor.
Basicamente é isso, quando temos um objeto que ao ser alterado uma propriedade dele, outros objetos precisam saber que este objeto foi alterado e precisam invocar um método deles assim que isso acontecer.
Em Javascript o Observer pode ser amplamente usado, porque lidamos com eventos mais do que qualquer coisa em Javascript.
É bastante necessário por exemplo, quando temos uma tela que é dinâmica a nível de Js, com paginação e tal. Quando por exemplo vamos para a próxima página da paginação, criamos novos elementos de acordo com novos dados e estes elementos perdem todos os eventos que os outros tinham na página anterior. Temos que ficar atualizando os eventos dos objetos quando nossa tela é alterada. O Pattern Observer pra isso é uma mão na roda.
Mas eu vou dar um exemplo bem simples, talvez não muito usual, mas para ser facilmente fixado e absorvido por vocês meus caros leitores.
Primeiro, nosso diagrama de classes do Pattern.
![]()
Agora nosso problema.
Temos dois objetos, Tela e Ponto, onde o objeto Tela será responsável por mostrar o objeto Ponto na tela. Porém o objeto Ponto tem um estado, que é a posição dele na tela, ele tem um atributo x e um atributo y que são suas coordenadas.
Sempre que eu alterar uma das suas coordenadas ( x ou y ) o objeto Tela precisa saber que isso aconteceu e atualizar sua posição na tela.
Portanto, vamos resolver esse problema usando o Pattern Observer.
Como o objeto Tela precisará observar o objeto Ponto, então será este nosso Observer. Então Ponto será nosso Subject.
Usando nosso “Javiani way” de construir Classes, o padrão Observer é construido da seguinte maneira:
var Interface = {
Observer : function(){
this.update = function(o){
throw new Error('Implemente Observer.update()')
}
},
Subject : function(){
var observers = {}
this.add_observer = function(o){ observers[o] = o }
this.rem_observer = function(o){ delete observers[o] }
this.notify = function(){
for(var x in observers)
observers[x].update(this)
}
}
}
Lembrando que Observer e Subject são interfaces, não classes. Portanto, teremos duas classes, Tela e Ponto, na qual cada uma delas IMPLEMENTARÁ um tipo de interface.
A interface acima Observer, terá um método obrigatório, que será o update, que fará alguma coisa com o objeto passado como argumento para o método. Este método será chamado pelo objeto Subject, notificando o Observer que um estado do Subject foi alterado.
A interface Subject, terá uma lista contendo todos os observers que um objeto Subject terá. E também terá 3 métodos. Um para adicionar um observer à lista, outro pra remover um observer da lista, e outro para passear sobre a lista e avisar à todos os seus Observers registrados que alguma coisa foi alterada, chamando o método update dos Observers passando o objeto que foi alterado. No nosso caso este objeto seria um objeto do tipo Ponto.
Vamos as classes Tela e Ponto então.
Classe ponto com um setter/getter para os pontos x e y. Ela implementará a interface Subject e notificará aos seus Observers que este ponto foi alterado no momento em que for setado um valor novo para x ou para y:
var Ponto = {
Class : function(){
Interface.Subject.apply( this )
var x = 0;
var y = 0;
this.x = function(i){
if(i){
x = i
this.notify()
}
else return x
}
this.y = function(i){
if(i){
y = i
this.notify()
}
else return y
}
},
New : function(){ return new this.Class() }
}
Agora a Classe Tela, que implementará Interface.Observer, terá um método update obrigatório que será chamado pelo Subject, mandando o subject como argumento.
var Tela = {
Class : function(object){
Interface.Observer.apply(this)
var html = object
this.desenha = function(o){
this.desenha[o.constructor].apply(this, arguments)
}
this.desenha[ Ponto.Class ] = function(o){
html.style.left = o.x() + 'px'
html.style.top = o.y() + 'px'
}
this.update = function(o){ this.desenha(o) }
},
New : function(object){ return new this.Class(object) }
}
Chamando um pouco a atenção à forma como montei a Tela, primeiro implementei a interface Observer, o construtor da classe Tela recebe como parâmetro um objeto html e o guarda numa variável privada. Esta classe tem um método sobrecarregado this.desenha. Por que imagino que este objeto Tela não desenhará apenas um ponto mas outros elementos futuramente. ( Se não conhece esta forma de overload, veja o artigo anterior à este )
Então o método sobrecarregado desenha que recebe um ponto, apenas definirá a posição em pixel, no atributo Css left, e top considerando que este objeto html esteja em posição absoluta ou relativa.
O método update será chamado pelo objeto Ponto assim que se alterar qualquer uma das suas coordenadas, isso será feito pelo método notify.
Pronto, agora vamos instanciar os objetos e fazer o padrão Observer funcionar:
window.onload = function(){
var dot = document.getElementById('dot')
var tela = Tela.New(dot) // Ou new Tela.Class(dot)
var ponto = Ponto.New() // Ou new Ponto.Class
ponto.add_observer(tela)
ponto.x(490)
ponto.y(80)
}
Aí está, pegamos um elemento html na tela, o passamos como argumento para o construtor de Tela e instanciamos o objeto tela.
Logo depois disso instanciamos o objeto ponto, adicionamos um observer à ele, que será o objeto tela.
Assim que setarmos um novo valor para x ou para y, o método notify em ponto será chamado, este fará uma chamada do método update de todos os observers registrados passando o objeto ponto como argumento.
O objeto tela ao ser notificado que houve uma alteração em ponto, desenhará na tela a nova posição do ponto. =)
Sacaram ?
Deixei disponível um exemplo deste código, abram-o no firefox de preferência, e setem um novo valor para o ponto, para x ou para y, ou para os dois no console. Deixei públicos os objetos tela e ponto.
Ahh, para não perder o costume de Cientista, aqui vai uma funçãozinha pra fazer o ponto animado =)
var t = 0.0
var fx = function(x, r, t){ return (x + r*Math.cos(t/2)) }
var fy = function(x, r, t){ return (x + r*Math.sin(t/2)) }
setInterval(function(){
if( t == 12 ) t = 0
ponto.x( fx(screen.width/2, 40, t) )
ponto.y( fy(screen.height/2 - 180, 40, t) )
t+=.5
}, 100)
Só colar no firebug e executar =D.
Um grande abraço galera, valeu!
Overload em Javascript
Publicado por javiani em Design Pattern, Idéias, Javascript, OOP, Técnicas de programação em abril 17, 2010
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