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 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
prototype
externamente, como nos métodosserFixe()
echuveiro()
acima. - Como tentei mostrar com a propriedade
Pessoa.prototype.pernas
e a funçãoamputate()
, propriedades do protótiplo são partilhadas por todas as instâncias ou objetos. A propriedadelk.pernas
tem o valor 2. Contudo, tentar alterar este valor usandogk.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 invocargk.amputar()
só remove uma perna ao Alex, mas não a Lisa.) Para alterar uma propriedade do protótipo, devemos usarPessoa.prototype.pernas=1
ou algo comothis.constructor.prototype.pernas=1
. (digo “algo como” porquethis.constructor
não está disponível dentro de funções privadas do objeto, uma vez quethis
se 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.cabelo
com 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 parafoo
ethis.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áveisidade
emaxIdade
são privadas;idade
só pode ser acedida externamente através degetIdade()
(não pode ser modificada) emaxIdade
nã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ávelvivo
não poderia ser atualizada, deixando o objeto Pessoa num estado incoerente.
Traduzido de OOP in JS