OOP em Javascript – Interfaces

Olá galera do javaspirit!

Hoje eu gostaria de compartilhar uma idéia que tive
para implementação de interfaces em Javascript, já que nosso foco agora é o reuso de código e fácil manutenção temos que pensar sempre na forma de Orientação à objetos.

Peço desculpas pelo post longo, mas eu preferi aumentá-lo um pouco porque interface é um conceito um pouco estranho e difícil de se entender e acho que vale a pena colocar pelo menos o desenvolvimento de um exemplo prático no final.

A Interface é uma forma de desacoplar a implementação da classe, ela provê uma forma de especificar que métodos um objeto deve ter.

Teóricamente é possível implementar interfaces em todas ou na maioria esmagadora das linguagens de programação, mas nem todas implementam uma palavra-chave ou uma estrutura definida para a utilização de interfaces. É o caso de Javascript, então como implementá-la?

O princípio primário da orientação à objetos diz que devemos programar para a interface e não para implementação. Rigidamente, a interface deve dizer quais métodos uma classe deve implementar.

Em Java, a interface tem esta cara:

public interface Motor {
  void acelerar();
  void frear();
}

Ou seja, definimos uma interface Motor, onde freia ou acelera.

Cumprindo o que prometi nos últimos posts, irei usar os mesmos exemplos para fixar as idéias de maneira mais fácil.

Recapitulando, tinhamos uma classe Carro e uma Classe Motocicleta que herdavam da classe abstrata Veiculo. Lembram?

Voltando à Interface em Java, se quisessemos que a classe Carro implementasse a interface Motor, faríamos:

public class Carro implements Motor{}

Deu pra notar que as interfaces não dizem como deve ser implementado, mas o que deve ser implementado. Esta classe Carro, precisaria obrigatóriamente implementar os métodos acelerar e frear.

Eu sinceramente não gosto de seguir sempre 100% às regras porque acredito que não há uma solução que sirva para todos os casos. Javascript tem uma notação diferente da maioria das linguagens, é um linguagem única, então tentar emular as interfaces de maneira que funcione como o Java por exemplo, pra mim é um erro. Javascript é extremamente flexível em grande parte por ser fracamente tipada, usar Interfaces é de certa forma forçar um comportamento de tipagem forte reduzindo a grande qualidade de javascript que é a sua flexibilidade.

Já vi algumas implementações de interfaces em javascript e todas tentavam copiar essa estrutura de java, contendo loops para verificar se a classe implementava os métodos e etc.
Eu particularmente acho todas ruins.

Bom, vou então mostrar como implemento as Interfaces em javascript e para isso vou precisar quebrar uma regra definida para interfaces. Peço então que abram um pouco suas mentes, porque seguir cegamente um conceito é restringir também sua criatividade.

1ª “As interfaces dizem o que deve ser implementado e não dizer como deve ser implementado”

Em javascript essa regra é inútil. Conseguimos passar os escopos, temos todo o potencial de programação funcional, podemos então definir um método que se aplica a qualquer classe que o implemente, se não se aplicar, o sobrecarregamos.

Então uma forma que vejo de implementar Interface de forma elegante copiando a estrutura de Java seria:

var Interface = {
	motor : function(name){
        this.frear = function(){
            throw "Implemente o método frear na Classe " + name
        }
        this.acelerar = function(){
            throw "Implemente o método acelerar na Classe " + name
        }
	}
}

Então, implementando esta interface na nossa classe Carro:

var Carro = {
	Class: function(){
		Interface.motor.apply(this, ["Carro"])
		var velocidade = 0

		this.velocidade = function(){
			return velocidade
		}
	}
}

Vejam que usei de novo o método apply, como fiz nas heranças no post passado. Ou seja, de forma semântica digo: “Interface motor se aplique à isso”. Vejam que a estrutura da Interface é muito similar a de Classes, não teve curva de aprendizado aqui, vocês já sabem como herdar os atributos se viram o post passado =).

Passei um argumento [“Carro”] como segundo parâmetro do método apply, porque montei os métodos da interface motor de forma a apresentar erros indicando onde precisaria implementar os métodos.

**O método apply precisa que os parâmetros da função chamada seja um array. Se preferir, poderia usar o “call” ao invés, então poderia passar “Carro” apenas como uma simples string.

Se eu fizesse então:

var carro = new Carro.Class
    carro . acelerar()

Geraria uma excessão, avisando que eu devo implementar o método acelerar na classe Carro. Na verdade o método está implementado, porém de forma a forçar o programador Js a sobrescrevê-lo.

Agora eu vou mostrar um exemplo de uso de interfaces do dia-a-dia, mostrando um uso para uma situação real. Eu estou de fato negligênciando a regra número um de interfaces, pois farei uma interface que não só vai obrigar a classe a ter certos métodos, como vou implementar estes métodos na própria interface. Come on!!!!

INTERFACES NA PRÁTICA

Vou criar uma classe para fazer o mascaramento de campos do tipo “text” em um formulário. E vou guardar nessa classe um objeto estático, chamado Methods, que armazenará o nome e as funções que farão as máscaras em formato json.


var Maskarade  = {

	Class : function( fields ){

		for(var x in fields)
			document.getElementById(x).onkeydown = function(){
				var self = this
				setTimeout(function(){
					self.value = 
					Maskarade.Methods[ fields[x] ]( self.value )
				},100)
			}
	},

	Methods : {
		digits_only: function(v){ return v.replace(/\D/g, '') },
		letters_only: function(v){ return v.replace(/\d/g, '') }
	}

}

Vou instanciá-la, tenho um form com um input com id igual à “number1”:

new Maskarade.Class({
    'number1' : 'digits_only'
})

Ao instanciar o construtor da classe vai percorrer o json passado como argumento, e para cada chave do json (que é o id do campo) eu vou “setar” o keydown deste campo e vou mascarar conforme a mascara do valor do json. É uma classe super simples.

Beleza, funciona perfeitamente. O código está correto, sem falhas. Mas eu quero que essa classe funcione com o jQuery, porque não quero fazer uma instância com muitos parâmetros, se meu site tivesse 300 campos a serem mascarados da mesma forma teria de botar 300 campos na instancia da classe, e porque meu chefe gosta de jQuery =).

Bom, não é uma boa embutir código jQuery ali na classe Maskarade, isso seria um erro. Nem seria interessante verificar se existe um jQuery e fazer um if onde caso contrário usa o jeito sem jQuery, pelo amor de Deus isso seria quase que matar uma criança recém nascida com 19 facadas no peito.

Estão entendendo onde a Interface entra? Conseguem visualizar uma forma de usá-la ali na classe? Vou dar uma força:

var Interface = {
	jQuery : function(){
		this.$ = jQuery
		this.keydown = function(el, f){
			el.keydown(f)
		}
	},
	Standard : function(){
		this.$ = function(el){
			return document.getElementById(el)
		}
		this.keydown = function(el, f){
			el.onkeydown = f
		}
	}
}

Olhaaaaaaaaaaaaaa
Olhaaaaaaaaaaaaaa

Vejam, eu tenho duas preocupações, a primeira é pegar o elemento, a segunda é dar um evento à ele. Logo, eu criei uma interface que vai ter dois métodos, um pega o elemento, o outro dá o evento de keypress neste elemento.

Então ali na interface tem um jeito padrão que vai pegar o elemento pelo id pelo método this.$.
E vai dar um evento à um elemento através do método this.keydown.

E tem o jeito jQuery de fazer as coisas. Apenas criei um apelido para referenciar jQuery através do nome this.$.
E criei um método que recebe um elemento e uma função e dá um keypress a lá jQuery way =).

Como fica a classe então abstraindo as formas de se pegar os elementos e adicionar um evento à eles?

Assim:

var Maskarade  = {

	Class : function( fields ){

		Interface.Standard.apply(this)

			for(var x in fields)
				this.keydown( this.$(x), function(){
					var self = this
					setTimeout(function(){
						self.value = 
						Maskarade.Methods[ fields[x] ]( self.value )
					},100)
				})
	},
	Methods: {
		digits_only: function(v){ return v.replace(/\D/g, '') },
		letters_only: function(v){ return v.replace(/\d/g, '') }
	}
}

Olha ali a chamada da interface Standard na Classe viram? Eu poderia também passar para o construtor da classe a interface e fazer uma lazy instance, criando um objeto que poderia usar jQuery ou não =). Depende do que planeja para o seu projeto.

this.$ vai pegar o elemento, e this.keydown vai receber o elemento, uma função e atrelar esta função ao evento deste elemento.

E daria para desacoplar ainda mais coisas dessa classe. Se eu tiver uma outra classe que faz a validações, então eu poderia usar os métodos digits_only e letters_only também não poderia ? Oras, vamos botar esse Methods para fora da classe e botá-lo dentro da Interface:

var Interface = {
	jQuery : function(){
		this.$ = jQuery
		this.keydown = function(el, f){
			el.keydown(f)
		}
	},
	Standard : function(){
		this.$ = function(el){
			return document.getElementById(el)
		}
		this.keydown = function(el, f){
			el.onkeydown = f
		}
	},
	Methods: {
		digits_only: function(v){ return v.replace(/\D/g, '') },
		letters_only: function(v){ return v.replace(/\d/g, '') }
	}
}

var Maskarade  = {

	Class : function( fields ){

		Interface.Standard.apply(this)

			for(var x in fields)
				this.keydown( this.$(x), function(){
					var self = this
					setTimeout(function(){
						self.value = 
						Maskarade.Methods[ fields[x] ]( self.value )
					},100)
				})
	},
	Methods: Interface.Methods
}

Beleza, agora sim.

Se eu implementasse então a interface jQuery na classe, poderia usar uma classe nos elementos html e botar mascaras em todos eles com uma chamada bem enxuta:

new Maskarade.Class({ '.numbers' : 'digits_only' })

\o/ YEAAAH!

Perceberam que a interface obrigou a utilização dos métodos e ainda definiu a sua implementação, quebrando completamente a regra número um das interfaces. Mas para que seguir à risca um conceito que em javascript não se encaixa muito bem?

Ás vezes acho que devemos deixar nossa intuição e criatividade nos guiar para resolver qualquer problema independente do paradigma da linguagem, é muito importante conhecer bem a linguagem que programa e usar conceitos existentes como inspiração e não seguí-las totalmente e cegamente. Neste caso, mostrei um exemplo onde o interessante é ter a implementação na própria interface.

Como a interface não passa de um singleton no js, você pode determinar o namespace que quiser para armazenar de forma semântica seus métodos. Eu não quis dificultar muito, então usei um namespace curto, Interface.nome_do_metodo.

Para quem acompanha meus posts e gosta, eu aconselho à ler e reler a minha implementação caso não tenham entendido, porque muitos dos Design Patterns existentes e que planejo falar mais pra frente utilizam o conceito de Interfaces.

E aí? gostaram? não gostaram? Comentem então =).

Um grande abraço.

Anúncios

4 comentários sobre “OOP em Javascript – Interfaces

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s