Classe, objeto literal ou closure? E getters/setters
Agora que você conhece as três formas de organizar dados e comportamento, vem a pergunta prática: qual usar em cada situação? E como controlar o acesso a um campo sem expor tudo cru?
Estudar esta lição interativa →Você agora tem três ferramentas para organizar dados e comportamento: o objeto literal (Módulo 6), a closure (início deste módulo) e a classe (lição passada). Elas se sobrepõem, e é natural ficar em dúvida sobre qual usar. Não existe uma regra rígida, mas existe um jeito prático de decidir, olhando para duas perguntas: "vou precisar de várias instâncias parecidas?" e "preciso de algum dado privado, inacessível de fora?".
| Situação | Melhor escolha | Por quê |
|---|---|---|
| Um dado único, sem repetição (configuração de um app, estado de uma tela) | Objeto literal | Simples, direto, sem o peso de criar uma classe para uma coisa só |
| Poucas variáveis privadas e um punhado de funções, sem necessidade de herança | Closure | Esconde o estado de verdade (ninguém de fora acessa a variável), sem sintaxe extra |
| Muitas instâncias do mesmo tipo, possivelmente com herança | Classe | Compartilha métodos de forma eficiente (via protótipo), deixa clara a relação entre os tipos e, com campos #privados, também esconde o estado de verdade |
Um guia prático, não uma lei: em projetos reais, as três formas convivem, cada uma no lugar onde encaixa melhor.
Uma última ferramenta das classes fecha bem essa comparação: getters e setters. Eles fazem um método se comportar como uma propriedade comum, mas executando código por trás. Um get roda quando alguém lê o campo (sem parênteses, como produto.preco, não produto.preco()); um set roda quando alguém escreve nele (produto.preco = 50). E é exatamente aí que mora a maior utilidade deles: o set é o lugar perfeito para validar um valor antes de aceitá-lo, algo que uma propriedade comum não permite fazer.
class Produto {
#preco; // campo PRIVADO: só o código de dentro da classe enxerga
constructor(nome, preco) {
this.nome = nome;
this.#preco = preco;
}
get preco() {
return this.#preco;
}
set preco(valor) {
if (valor < 0) {
throw new Error("Preço não pode ser negativo");
}
this.#preco = valor;
}
}
const p = new Produto("Caneta", 3);
console.log(p.preco); // 3 (lê como propriedade comum, mas chama o getter)
p.preco = 5; // escreve como propriedade comum, mas chama o setter
console.log(p.preco); // 5
try {
p.preco = -10; // o setter recusa
} catch (e) {
console.log("Erro:", e.message);
}No exemplo, o valor mora no campo #preco. O # na frente do nome cria um campo privado: só o código de dentro da classe consegue ler ou escrever; escrever p.#preco do lado de fora nem chega a rodar, o JavaScript acusa erro de sintaxe. Por isso o único caminho para mudar o preço é o set, que valida. Em códigos mais antigos você vai encontrar _preco (com sublinhado): é só uma convenção para dizer "não mexa aqui", mas nada impede quem quiser de ler ou alterar p._preco direto, passando por cima do setter. Prefira o # em código novo.
Getter e setter são ótimos quando existe uma regra de verdade por trás (validar, calcular algo derivado, disparar um efeito colateral). Se o campo é só um valor simples, sem nenhuma lógica de leitura ou escrita, uma propriedade comum (this.nome = nome) é mais direta e mais fácil de ler. Adicionar getter/setter "porque parece mais profissional" só adiciona código sem benefício real.
// Sua vez! Complete o getter "fahrenheit".
class Termometro {
#celsius; // privado: só a classe mexe
constructor(celsius) {
this.#celsius = celsius;
}
get celsius() {
return this.#celsius;
}
get fahrenheit() {
// calcule e devolva: celsius * 9/5 + 32
}
}
const t = new Termometro(100);
console.log(t.fahrenheit); // deve dar 212Complete o getter fahrenheit e clique em Rodar.