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.nomeVariavele 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 objetogk, e não a toda a classePessoa. Para outras pessoas, o métodoserFixe()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ódigoalert(gk+' é fixe.')coloque o nome ‘Alex’ lá, e é equivalente aalert(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
prototypeexternamente, como nos métodosserFixe()echuveiro()acima. - Como tentei mostrar com a propriedade
Pessoa.prototype.pernase a funçãoamputate(), propriedades do protótiplo são partilhadas por todas as instâncias ou objetos. A propriedadelk.pernastem o valor 2. Contudo, tentar alterar este valor usandogk.pernas=1ou (no objeto Pessoa)this.pernas=1acaba por criar uma nova propriedade pública específica para essa instância. (É por isso que invocargk.amputar()só remove uma perna ao Alex, mas não a Lisa.) Para alterar uma propriedade do protótipo, devemos usarPessoa.prototype.pernas=1ou algo comothis.constructor.prototype.pernas=1. (digo “algo como” porquethis.constructornão está disponível dentro de funções privadas do objeto, uma vez quethisse refere ao objeto todo.) - Sempre que uma função anónima é declarada em linha com
foo = function(p1,p2){ código }
o construtornew 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.cabelocom um valor qualquer NÃO substitui a variável privadacabelo. Embora seja uma ideia parva, é possível ter ambas as variáveis, pública e privada, com o mesmo nome. Por exemplo, o métodogritat()na classe seguinte, tem valores diferentes parafooethis.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 demaisVelho(),toString(),getNome(),comer(),exercício(),peso(),getCor(),getIdade(), andmuitoTempo(). Para cada Pessoa, de cada vez. Isto contrasta com os métodos públicos (existe apenas uma cópia deserFixe()echuveiro()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áveisidadeemaxIdadesão privadas;idadesó pode ser acedida externamente através degetIdade()(não pode ser modificada) emaxIdadenão pode ser lida nem modificada externamente. Torná-las públicas iria permitir que qualquer código executassegk.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ávelvivonão poderia ser atualizada, deixando o objeto Pessoa num estado incoerente.
Traduzido de OOP in JS