Javascript orientado a objetos – II

No  artigo anterior vimos como criar classes em JS, incluindo propriedades e métodos públicos, privilegiados e privados. Este artigo explica a herança em Javascript.

Sumário

  • É possível estabelecer uma relação de herança entre classes com NomeCLasseFilha.prototype = new ClassePai();.
  • É necessário fazer reset da propriedade constructor usando NomeClasseFilha.prototype.constructor=NomeClasseFilha.
  • Podem invocar-se métodos das classes ancestrais que a classe filha tenha modificado usando o método Function.call().
  • O Javascript não tem suporte para métodos protegidos.

Exemplo

Segue-se um exemplo de herança entre duas classes:

function Mamifero(nome){
	this.nome=nome;
	this.descendencia=[];
}
Mamifero.prototype.temUmFilho=function(){
	var novoFilho=new Mamifero("Filho "+this.nome);
	this.descendencia.push(novoFilho);
	return novoFilho;
}
Mamifero.prototype.toString=function(){
	return '[Mamifero "'+this.nome+'"]';
}


Gato.prototype = new Mamifero();       // É aqui que a herança acontece
Gato.prototype.constructor=Gato;       // Caso contrário as instâncias de Gato usariam o construtor de Mamifero
function Gato(nome){
	this.nome=nome;
}
Gato.prototype.toString=function(){
	return '[Gato "'+this.nome+'"]';
}


var umAnimal = new Mamifero('Sr. Biggles');
var bicho = new Gato('Felix');
alert('umAnimal is '+umAnimal);        // resulta em 'umAnimal é [Mamifero "Sr. Biggles"]'
alert('bicho is '+bicho);              // resulta em 'bicho é [Gato "Felix"]'

bicho.temUmFilho();                    // chama um método herdado de Mamifero
alert(bicho.descendencia.length);      // mostra que o gato agora tem um filho
alert(bicho.descendencia[0]);          // resulta em '[Mamifero "Filho Felix"]'

Utilização da propriedade .constructor

Atente-se na última linha do exemplo anterior. O filho de um Gato deve ser um Gato, certo? Embora o método temUmFilho() funcione, esse método cria especificamente um new Mamifero. Embora pudéssemos criar um novo método temUmFilho() para a subclasse Gato como this.descendencia.push(new Gato("Filho "+this.nome)), seria preferível que a classe ancestral criasse um objeto do tipo correto.

Qualquer instância ou objeto em JS tem uma propriedade chamada constructor que aponta para a sua classe pai. Por exemplo, umAnimal.constructor==Mamifero é verdade. Com este conhecimento, podemos rescrever o método temUmFilho() assim:

Mamifero.prototype.temUmFilho=function(){
	var novoFilho=new this.constructor("Filho "+this.nome);
	this.descendencia.push(novoFilho);
	return novoFilho;
}
...
bicho.temUmFilho();                    // O mesmo que antes: chama o método herdado de Mamifero
alert(bicho.descendencia[0]);          // Agora resulta em '[Gato "Filho Felix"]'

Invocar ‘super’ métodos

Vamos estender o exemplo para quando os gatinhos filhos são criados, eles ‘miam’ logo assim que nascem. Para isso, vai ser necessário escrever um método próprio Gato.prototype.temUmFilho(), que seja capaz de invocar o método original Mamifero.prototype.temUmFilho():

Gato.prototype.temUmFilho=function(){
	Mamifero.prototype.temUmFilho.call(this);
	alert("miau!");
}

O código acima pode parecer bizarro. O Javascript não tem a prorpiedade ‘super’, que aponte para a classe pai. Ao invés, usa-se o método call() de uma Function do objeto, que permite invocar uma função usando um objeto diferente como contexto para essa função. Se for necessário passar parâmetros para essa função, vão após o ‘this’. Para mais informações sobre o método Function.call(), ver MSDN docs for call().

Criar uma propriedade ‘super’

Em vez de ter que saber que Gato herda de Mamifero, e ter que escrever Mamifero.prototype de cada vez que quisermos invocar um método ancestral, não seria mais interessante ter uma propriedade de gato para a sua superclasse? Os mais familiarizados com linguagens orientadas a objetos seriam tentados a chamar a essa propriedade ‘super’, mas o JS reserva esta palavra para uso futuro. A palavra ‘parent’, embora seja usada nalguns itens do DOM, pode ser usada livremente em JS, portanto vamos usar parent neste exemplo:

Gato.prototype = new Mamifero();
Gato.prototype.constructor=Gato;
Gato.prototype.parent = Mamifero.prototype;
...
Gato.prototype.temUmFilho=function(){
	var osFilhos = this.parent.temUmFilho.call(this);
	alert("miau!");
	return osFilhos;
}

Falsas classes virtuais puras

Algumas linguagens de programação orientadas a objetos têm o conceito de classe virtual pura, que é uma classe que não pode ser instanciada, mas apenas herdada. Por exemplo, podemos ter a classe CoisaViva da qual a classe Mamifero herda, mas não queremos que seja possível instanciar CoisaViva sem especificar o tipo de coisa. É possível fazer isto em JS criando a classe virtual a partir de um objeto ao invés de uma função.

O exemplo seguinte mostra como isto pode ser usado para simular uma superclasse virtual pura:

CoisaViva = {
	nascer : function(){
		this.vivo=true;
	}
}
...
Mamifero.prototype = CoisaViva;
Mamifero.prototype.parent = CoisaViva;   //Nota: não 'CoisaViva.prototype'
Mamifero.prototype.temUmFilho=function(){
	this.parent.nascer.call(this);
	var novoFilho=new this.constructor("Filho "+this.nome);
	this.descendencia.push(novoFilho);
	return novoFilho;
}

Com o código acima,, a instrução seguinte var espirito = new CoisaViva() daria erro, pois CoisaViva não é uma função, e por isso não pode ser usada como um construtor.

Herança conveniente

Ao invés de escrever 3 linhas de cada vez que queremos que uma classe herde de outra, é mais conveniente esteder o objeto Function para fazer isso por nós:

Function.prototype.herdaDe = function( objetoOuClassePai ){
	if ( objetoOuClassePai.constructor == Function )
	{
		//Herança normal
		this.prototype = new objetoOuClassePai;
		this.prototype.constructor = this;
		this.prototype.parent = objetoOuClassePai.prototype;
	}
	else
	{
		//Herança pura virtual
		this.prototype = objetoOuClassePai;
		this.prototype.constructor = this;
		this.prototype.parent = objetoOuClassePai;
	}
	return this;
}
//
//
CoisaViva = {
	beBorn : function(){
		this.vivo = true;
	}
}
//
//
function Mamifero(nome){
	this.nome=nome;
	this.descendencia=[];
}
Mamifero.herdaDe( CoisaViva );
Mamifero.prototype.temUmFilho=function(){
	this.parent.nascer.call(this);
	var novoFilho = new this.constructor( "Filho " + this.nome );
	this.descendencia.push(novoFilho);
	return novoFilho;
}
//
//
function Gato( nome ){
	this.nome=nome;
}
Gato.herdaDe( Mamifero );
Gato.prototype.temUmFilho=function(){
	var osGatos = this.parent.temUmFilho.call(this);
	alert("miau!");
	return osGatos;
}
Gato.prototype.toString=function(){
	return '[Gato "'+this.nome+'"]';
}
//
//
var felix = new Gato( "Felix" );
var gatos = felix.temUmFilho( ); // mew!
alert( kitten );                 // [Gato "Filho Felix"]

Este método deve ser chamado imediatamente após o construtor, e antes de estender o protótipo para o objeto.

Métodos protegidos?

Algumas linguagens de programação orientadas a objetos têm o conceito de métodos protegidos — métodos que existem numa classe pai ou ancestral e que só podem ser invocados por descendentes do objeto, mas não por objetos externos. O JS não suporta estas funcionalidades. Quem precisar disto, terá que escrever a sua própria framework, garantindo que cada classe tem um ‘parent’ ou uma propriedade semelhante, e sobe a árvore à procura de ancestrais, verificando se o objeto invocador é do mesmo tipo. Fazível, mas não agradável.

Traduzido de OOP in JS – 2

Deixar uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *