OOP em Javascript – Classes Abstratas

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.