Recapitulando... "Quando definimos os atributos de um objeto, devemos garantir que alterar os valores desses atributos sejam responsabilidade exclusiva do próprio objeto. O encapsulamento, portanto, é o conceito de proteger os atributos de um objeto."
A definição de encapsulamento é "a ação de colocar algo dentro ou como se estivesse em uma cápsula". Remover o acesso a partes do seu código e tornar as coisas privadas é exatamente o que o encapsulamento faz.
O encapsulamento se trata de um dos elementos que adicionam segurança à aplicação em uma programação orientada a objetos pelo fato de esconder as propriedades, criando uma espécie de caixa preta.
A maior parte das linguagens orientadas a objetos implementam o encapsulamento baseado em propriedades privadas, ligadas a métodos especiais chamados getters e setters, que irão retornar e setar o valor da propriedade, respectivamente. Essa atitude evita o acesso direto a propriedade do objeto, adicionando uma outra camada de segurança à aplicação.
Para fazermos um paralelo com o que vemos no mundo real, temos o encapsulamento em outros elementos. Por exemplo, quando clicamos no botão ligar da televisão, não sabemos o que está acontecendo internamente. Podemos então dizer que os métodos que ligam a televisão estão encapsulados.
Agora, voltando na nossa analogia do carro, sabemos que ele possui atributos e métodos, ou seja, características e comportamentos. Os métodos do carro, como acelerar, podem usar atributos e outros métodos do carro como o tanque de gasolina e o mecanismo de injeção de combustível, uma vez que acelerar gasta combustível.
No entanto, se alguns desses atributos ou métodos forem facilmente visíveis e modificáveis, como o mecanismo de aceleração do carro, isso pode dar liberdade para que alterações sejam feitas, resultando em efeitos colaterais imprevisíveis. Nessa analogia, uma pessoa pode não estar satisfeita com a aceleração do carro e modifica a forma como ela ocorre, criando efeitos colaterais que podem fazer o carro nem andar, por exemplo.
Dizemos, nesse caso, que o método de aceleração do seu carro não é visível por fora do próprio carro. Na POO, um atributo ou método que não é visível de fora do próprio objeto é chamado de "privado" e quando é visível, é chamado de "público".
Mas então, como sabemos como o nosso carro acelera? É simples: não sabemos. Nós só sabemos que para acelerar, devemos pisar no acelerador e de resto o objeto sabe como executar essa ação sem expor como o faz. Dizemos que a aceleração do carro está encapsulada, pois sabemos o que ele vai fazer ao executarmos esse método, mas não sabemos como - e na verdade, não importa para o programa como o objeto o faz, só que ele o faça.
O mesmo vale para atributos. Por exemplo: não sabemos como o carro sabe qual velocidade mostrar no velocímetro ou como ele calcula sua velocidade, mas não precisamos saber como isso é feito. Só precisamos saber que ele vai nos dar a velocidade certa. Ler ou alterar um atributo encapsulado pode ser feito a partir de getters e setters (colocar referência).
Esse encapsulamento de atributos e métodos impede o chamado vazamento de escopo, onde um atributo ou método é visível por alguém que não deveria vê-lo, como outro objeto ou classe. Isso evita a confusão do uso de variáveis globais no programa, deixando mais fácil de identificar em qual estado cada variável vai estar a cada momento do programa, já que a restrição de acesso nos permite identificar quem consegue modificá-la.
Vocês já aprenderam os conceitos a seguir em aulas anteriores, mas vamos recapitulá-las para ter certeza de que entendemos bem para poder utilizá-las daqui em diante.
Modificadores de acesso são uma feature muito nova no JavaScript (lançada no ES2022), mas que já está disponível para cerca de 93% dos usuários totais, segundo o Can I Use
.
Uma propriedade ou método pode ser:
- Público: o padrão do JS, acessível de qualquer lugar.
- Privado: acessível apenas de dentro da classe.
Para tornar uma propriedade privada, usamos #
antes do nome. Antes dessa feature ser implementada, era comum usar _
na frente do nome de uma propriedade para indicar que ela era privada.
Exemplo:
class Gato {
nome;
idade;
cor;
castrado;
#segredo;
constructor(nome, idade, cor, castrado, segredo) {
this.nome = nome;
this.idade = idade;
this.cor = cor;
this.castrado = castrado;
this.#segredo = segredo;
this.cativado = false;
}
miar() {
console.log('Miau :3');
}
brincar() {
this.cativado = true;
console.log(`${this.nome} agora confia em você!`);
}
fofocar() {
if (this.cativado) {
let fofoca = `...fui eu quem ${this.#segredo}`;
console.log('Preciso confessar uma coisa...');
console.log(fofoca);
} else {
console.log('Hmmm... suspeito');
}
}
}
As classes JavaScript contam com dois métodos especiais:
- um com o prefixo
get
, que tem a função de retornar um valor de um parâmetro. - outro com prefixo
set
que serve para atribuir um valor a um parâmetro.
Nós chamamos eles de Getters e Setters, pois eles tem a função de fazer um get
(pegar) ou um set
(atribuir).
Ambos funcionam como se fossem uma propriedade da classe.
Esses métodos são ideais para serem utilizados, quando temos parâmetros privados.
O getter, com a sintaxe get é associado a uma função que será chamada quando a propriedade em questão for acessada e solicitada de forma dinâmica. É possível utilizá-la para retornar o status de uma variável interna, sem utilizar métodos de forma explícita. Da seguinte forma:
class Curso {
materia;
professor;
duracao;
constructor(materia, professor, duracao) {
this.materia = materia;
this.professor = professor;
this.duracao = duracao;
}
get prof() {
return this.professor;
}
}
let poo = new Curso(
'Programação orientada a objetos',
'Rafaella',
'1 semestre'
);
console.log(poo.prof); //Rafaella
Nesse exemplo apenas utilizamos o getter para retornar um valor que já havia sido declarado de forma fixa. Perceba que podemos utilizar o método get tanto com atributos públicos quanto provados. Além disso, o nome da função get não precisa ser o mesmo nome do atributo que ela vai retornar.
Observe agora o seguinte exemplo:
class Boletim {
constructor(participacao, prova, trabalho) {
this.participacao = participacao;
this.prova = prova;
this.trabalho = trabalho;
}
get media() {
return parseInt((this.participacao + this.prova + this.trabalho) / 3);
}
}
let boletimSemestral = new Boletim(8, 6, 7.5);
console.log(boletimSemestral.media); //7
Perceba agora que o método get está sendo utilizado para buscar um valor que sequer é um parâmetro.
Por fim, observe o seguinte exemplo:
class Conta {
ehAdm;
#senha = 123;
constructor(ehAdm) {
this.ehAdm = ehAdm;
}
get senha() {
if(this.ehAdm) {
return this.#senha;
}
}
}
const conta1 = new Conta(true)
console.log(conta1.senha) //123
const conta2 = new Conta(false)
console.log(conta2.senha) //undefined
Alguns pontos importantes a serem destacados para o uso de getters são:
- Pode ter um identificador do tipo numérico ou string (aconselho a usar string).
- Não deve ter nenhum parâmetro.
- Não pode ser utilizado mais de um getter para uma mesma propriedade, assim como não pode haver uma propriedade comum com o mesmo nome do getter.
Geralmente usados junto com os getters, os setters são utilizados para definirem valores para uma propriedade específica.
Observe o exemplo:
class Aluno {
constructor(nome, curso, semestre){
this.nome = nome,
this.curso = curso,
this.semestre = semestre
}
set nomeAluno(nomeAluno) {
this.nome = nomeAluno
}
}
let lucas = new Aluno('', 'Engenharia', 5)
lucas.nomeAluno = 'Lucas'
console.log(lucas.nome) //Lucas
Perceba que a propriedade nome
foi modificada através de uma função, e não diretamente.
Agora, utilizando esse conceito com propriedades privadas:
class Conta {
ehAdm;
#saldo = 0;
constructor(ehAdm) {
this.ehAdm = ehAdm;
}
set saldo(novoSaldo) {
if(this.ehAdm) {
this.#saldo = novoSaldo;
}
}
get saldo() {
return this.#saldo;
}
}
const conta1 = new Conta(true)
console.log(conta1.saldo) // 0
conta1.saldo = 100;
console.log(conta1.saldo) // 100
const conta2 = new Conta(false)
console.log(conta2.saldo) // 0
conta2.saldo = 100;
console.log(conta2.saldo) // 0
Alguns pontos importantes a serem destacados para o uso de setters são:
- Pode ter um identificador do tipo numérico ou string.
- Deve ter exatamente um parâmetro.
- Não pode ter a mesma nomenclatura para propriedade e função (então por que no exemplo conseguimos utilizar a palavra saldo?).
Agora que reforçamos os conceitos de get e set, podemos ver outros exemplos de como utilizá-los juntos:
class User extends Person {
name;
age;
#password;
constructor(name, age, email, password) {
this.name = name;
this.age = age;
this.email = email;
this.#password = password;
}
speak() {
console.log(`A pessoa de nome ${this.name} está falando`);
}
#encryptPassword() {
return (this.#password = `*** ${this.#password} + ***`);
}
get password() {
//Aqui dentro, podemos ter verificações antes de retornar a senha para quem está pedindo, para tornar a nossa aplicação mais segura
return this.#password
}
set password(newPassword) {
//Aqui dentro, podemos ter verificações antes de trocar a senha para quem está solicitando a troca, para tornar a nossa aplicação mais segura
this.#password = newPassword
}
}
Lembrete para a prô: perguntar se as alunas já aprenderam sobre exportar e importar com o
module.exports
erequire
. Caso não tenham aprendido, explicar e ensinar como se faz.
- Alura - This, Getters e Setters nas classes Javascript
- Dev Media - Programação Orientada a Objetos
- Dev Media - Os 4 pilares da Programação Orientada a Objetos
- Alura - POO: o que é programação orientada a objetos?
- Kenzie - O que é programação orientada a objetos e quais são seus pilares?
Desenvolvido com 💜