Javascript orientado a objetos – I

JS orientado a objetos, Parte 1 : variáveis e métodos públicos e privados

Neste artigo mostramos como criar variáveis e métodos públicos e privados em classes de Javascript.

Sumário

  • variáveis privadas são declaradas com a palavra reservada ‘var’ dentro do objeto, e só podem ser acedidas por funções privadas e métodos privilegiados.
  • funções privadas são declaradas em linha dentro do construtor do objeto (ou alternativamente podem ser declaradas via var nomeFuncao=function(){...}) e só podem ser chamadas por métodos privilegiados (incluindo o construtor do objeto).
  • métodos privilegiados são declarados com this.nomeMetodo=function(){...} e podem ser invocados por código externo ao objeto.
  • propriedades públicas são declaradas com this.nomeVariavel e podem ser lidas e escritas de fora do objeto.
  • métodos públicos são definidos por NomeClasse.prototype.nomeMetodo = function(){...} e podem ser invocados de fora do objeto.
  • propriedades do protótipo são definidas por NomeClasse.prototype.nomePropriedade = valor
  • propriedades estáticas são definidas por NomeClasse.nomePropriedade = valor

Exemplo

Neste exemplo, o nome e cor do cabelo da pessoa são definidos no início e não podem ser alterados. Uma pessoa é criada no ano 1 e com uma idade máxima escondida. A pessoa tem um peso que é modificado pelo que come (triplicando o seu peso) ou pelo exercício (diminuindo para metade). De cada vez que a pessoa come ou exercita, a sua idade aumenta uma ano. O objeto pessoa tem uma propriedade ‘roupa’ que qualquer um pode modificar, assim como um fatorLixo que qualquer um pode modificar manualmente, mas que cresce sempre que a pessoa come ou exercita, e é reduzido pelo uso do método chuveiro().

Código exemplo

function Pessoa(n,cabelo){
	this.constructor.populacao++;

	// ************************************************************************
	// Variáveis e funções privadas
	// só me´todos privilegiados podem ver/editar/invocar
	// ***********************************************************************
	var vivo=true, idade=1;
	var maxIdade=70+Math.round(Math.random()*15)+Math.round(Math.random()*15);
	function maisVelho(){ return vivo = (++idade <= maxIdade) }

	var meuNome=n?n:"Maria João";
	var peso=1;


	// ************************************************************************
	// Métodos privilegiados
	// Podem ser invocados publicamente e aceder a itens privados
	// não podem ser alterados; podem ser substituídos por versões públicas
	// ************************************************************************
	this.toString=this.getNome=function(){ return meuNome }

	this.comer=function(){
		if (maisVelho()){
			this.fatorLixo++;
			return peso*=3;
		} else alert(meuNome+" não pode comer, morreu!");
	}
	this.exercicio=function(){
		if (maisVelho()){
			this.fatorLixo++;
			return peso/=2;
		} else alert(meuNome+" não pode comer, morreu!");
	}
	this.pes=function(){ return peso; }
	this.getCor=function(){ return cor; }
	this.getIdade=function(){ return idade; }
	this.muitoTempo=function(){ idade+=50; this.fatorLixo=10; }


	// ************************************************************************
	// Propriedades públicas -- Qualquer um pode ler/escrever
	// ************************************************************************
	this.roupa="nada/nú";
	this.fatorLixo=0;
}


// ************************************************************************
// Métodos públicos -- Qualquer um pode ler/escrever
// ************************************************************************
Person.prototype.serFixe = function(){ this.roupa="cáqui e camisa preta" }
Person.prototype.chuveiro = function(){ this.fatorLixo=2 }
Person.prototype.mostraPernas = function(){ alert(this+" tem "+this.pernas+" pernas") }
Person.prototype.amputar = function(){ this.pernas-- }


// ************************************************************************
// Propriedades do protótipo -- Qualquer um pode ler/Escrever (mas podem ser reescritas)
// ************************************************************************
Person.prototype.pernas=2;


// ************************************************************************
// Propriedades estáticas -- Qualquer um pode ler/Escrever
// ************************************************************************
Person.populacao = 0;



// Aqui vai o código que usa a classe Pessoa
function GereVidaDoAlex(){
	var gk=new Pessoa("Alex","castanho");       //Nova instância da classe Pessoa
	var lk=new Pessoa("Maria","ruiva");         //Nova instância da classe Pessoa
	alert("Neste momento há "+Pessoa.populacao+" pessoas");

	gk.mostraPernas(); lk.mostraPernas();                 //Ambos partilham a variável comum 'Pessoa.prototype.pernas' quando olham para 'this.legs'

	gk.cabelo = "castanho";                               //afeta uma variável pública, mas não reescreve a variável 'cor'.
	alert("A cor do cabelo de "+gk+" é "+gk.getCor());    //Devolve 'castanho' da variável privada 'cor' criada no início..
	gk.comer(); gk.comer(); gk.comer();                   //o peso é 3...depois 9...depois 27
	alert(gk+" pesa "+gk.peso()+" quilos e tem um fator lixo de "+gk.fatorLixo);

	gk.exercicio();                                // o peso é agora 13.5
	gk.serFixe();                                  //a roupa foi atualizada para a moda atual
	gk.roupa="Roupa de cafetão";                   //A roupa é uma variável pública que pode ser atualizada para qualquer valor
	gk.chuveiro();
	alert("A atual tecnologia de chuveiro obteve "+gk+" para um fator lixo de "+gk.fator Lixo);

	gk.muitoTempo();                              //Passam 50 anos
	Person.prototype.chuveiro=function(){         //A tecnologia de chuveiro melhorou para todos
		this.fatorLixo=0;
	}
	gk.serFixe=function(){                        //O Alex tem novas ideias sobre moda
		this.roupa="Roupa de lata";
	};

	gk.serFixe(); gk.chuveiro();
	alert("O elegante "+gk+" com "
		+gk.getIdade()+" anos, veste agora "
		+gk.roupa+" com fator lixo "
		+gk.fatorLixo);

	gk.amputar();                                 //Usa a propriedade do protótipo e cria uma propriedade pública
	gk.mostraPernas(); lk.mostraPernas();         //A Lisa ainda tem a propriedade do protótipo

	gk.muitoTempo();                              //Passm 50 anos...O Alex tem agora 100 anos
	gk.comer();                                   //Queixa-se sobre idade extrema, morte e dificuldade em comer.
}

Notas

  • maxIdade é uma variável privada, sem método de acesso privilegiado; ou seja, não há forma pública de alterá-la ou consultá-la.
  • cabelo é uma variável privada definida como argumento para o construtor. Variáveis passadas para o construtor estão disponíveis no objeto como variáveis privadas.
  • O método serFixe() 'Roupa de Lata' foi aplicado apenas ao objeto gk, e não a toda a classe Pessoa. Para outras pessoas, o método serFixe() usa a roupa original ‘cáqui e camisa preta’ que o Alex abandonou mais tarde.
  • Notar a chamada implícita ao métido gk.toString() quando se usa a concatenação de strings. É isto que permite que o código alert(gk+' é fixe.') coloque o nome ‘Alex’ lá, e é equivalente a alert(gk.toString()+' é fixe.'). Qualquer objeto de qualquer tipo em JS tem um método .toString(), mas pode ser substituído por um nosso.
  • Não é possível (em princípio) definir métodos públicos de uma classe dentro do seu construtor.  É necessário usar a propriedade prototype externamente, como nos métodos serFixe() e chuveiro() acima.
  • Como tentei mostrar com a propriedade Pessoa.prototype.pernas e a função amputate(), propriedades do protótiplo são partilhadas por todas as instâncias ou objetos. A propriedade lk.pernas tem o valor 2. Contudo, tentar alterar este valor usando gk.pernas=1 ou (no objeto Pessoa) this.pernas=1 acaba por criar uma nova propriedade pública específica para essa instância. (É por isso que invocar gk.amputar() só remove uma perna ao Alex, mas não a Lisa.) Para alterar uma propriedade do protótipo, devemos usar Pessoa.prototype.pernas=1 ou algo como this.constructor.prototype.pernas=1. (digo “algo como” porque this.constructor não está disponível dentro de funções privadas do objeto, uma vez que this se refere ao objeto todo.)
  • Sempre que uma função anónima é declarada em linha com
    foo = function(p1,p2){ código }
    o construtor new Function() NÃO é equivalente, por exemplo, a
    foo = new Function('p1','p2','código');
    uma vez que o último corre no âmbito global – ao invés de herdar o contexto da função construtor – não permitindo assim que ele aceda às variáveis privadas.
  • Como indicado nos comentários acima, o ato de afetar gk.cabelo com um valor qualquer NÃO substitui a variável privada cabelo. Embora seja uma ideia parva, é possível ter ambas as variáveis, pública e privada, com o mesmo nome. Por exemplo, o método  gritat() na classe seguinte, tem valores diferentes para foo e this.foo:
    function ClasseParva(){
      var foo = "interno";
      this.foo = "externo";
      this.gritar=function(){ alert("O foo interno é "+foo+"\nO foo externo é "+this.foo) }
    }
  • Funções privadas e métodos privilegiados, tal como variáveis privadas e propriedades públicas, são instanciados com cada novo objeto criado. Por isso, de cada vez que se cria uma pessoa com new Pessoa() são criadas novas cópias de maisVelho(), toString(), getNome(), comer(), exercício(), peso(), getCor(), getIdade(), and muitoTempo(). Para cada Pessoa, de cada vez. Isto contrasta com os métodos públicos (existe apenas uma cópia de serFixe() e chuveiro() independentemente do número de objetos de Pessoa que sejam criados) e por motivos de consumo de memória e desempenho é preferível abandonar algum grau de proteção dos objetos a favor do uso de métodos públicos. Note-se que, para isso, é necessário tornar públicas as variáveis privadas (uma vez que sem métodos de acesso privilegiado não haveria maneira de usá-las) para que os métodos públicos sejam capazes de ter acesso a elas; o que também permite que código externos veja e destrua essas variáveis. A otimização de memória e desempenho, recorrendo ao uso exclusivo de propriedades públicas, tem consequências que podem tornar o código menos robusto. Por exemplo, no código acima, as variáveis idade e maxIdade são privadas; idade só pode ser acedida externamente através de getIdade() (não pode ser modificada) e maxIdade não pode ser lida nem modificada externamente. Torná-las públicas iria permitir que qualquer código executasse gk.maxIdade=1; gk.idade=200; o que não faz sentido (não deve ser possível manipular a idade ou a esperança de vida de alguém diretamente), e como consequência, a variável vivo não poderia ser atualizada, deixando o objeto Pessoa num estado incoerente.
Traduzido de OOP in JS

Deixar uma resposta

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