Getters, setters e campos privados em classes

1. Introdução aos Getters e Setters em JavaScript

Getters e setters são métodos especiais que permitem controlar o acesso a propriedades de um objeto. Em classes ES6, eles são definidos com as palavras-chave get e set seguidas do nome da propriedade.

class Pessoa {
  constructor(nome, idade) {
    this._nome = nome;
    this._idade = idade;
  }

  get nome() {
    return this._nome;
  }

  set nome(novoNome) {
    if (typeof novoNome !== 'string' || novoNome.trim() === '') {
      throw new Error('Nome inválido');
    }
    this._nome = novoNome;
  }

  get idade() {
    return this._idade;
  }

  set idade(novaIdade) {
    if (typeof novaIdade !== 'number' || novaIdade < 0 || novaIdade > 150) {
      throw new Error('Idade inválida');
    }
    this._idade = novaIdade;
  }
}

const pessoa = new Pessoa('João', 30);
console.log(pessoa.nome); // "João"
pessoa.idade = 31; // setter valida e atualiza
// pessoa.idade = -5; // Lançaria erro: "Idade inválida"

A principal diferença entre propriedades regulares e acessadas via get/set é que getters e setters permitem executar lógica personalizada durante a leitura e escrita de valores, enquanto propriedades regulares são apenas armazenamento direto.

2. Getters e Setters: Encapsulamento e Lógica Personalizada

Getters são excelentes para computar valores sob demanda, sem armazenar dados redundantes.

class Retangulo {
  constructor(largura, altura) {
    this._largura = largura;
    this._altura = altura;
  }

  get area() {
    return this._largura * this._altura;
  }

  get perimetro() {
    return 2 * (this._largura + this._altura);
  }

  set largura(valor) {
    if (valor <= 0) throw new Error('Largura deve ser positiva');
    this._largura = valor;
  }

  set altura(valor) {
    if (valor <= 0) throw new Error('Altura deve ser positiva');
    this._altura = valor;
  }
}

const ret = new Retangulo(10, 5);
console.log(ret.area); // 50 (calculado na hora)

Boas práticas importantes:
- Evite efeitos colaterais inesperados em getters (eles devem ser "puros")
- Mantenha setters simples e previsíveis
- Use setters para validação, transformação de dados ou notificação de mudanças

3. Campos Privados em Classes (#)

Desde o ES2022, JavaScript suporta campos privados reais com o prefixo #. Esses campos são inacessíveis fora da classe.

class ContaBancaria {
  #saldo = 0; // campo privado

  constructor(titular, saldoInicial = 0) {
    this.titular = titular;
    this.#saldo = saldoInicial;
  }

  depositar(valor) {
    if (valor <= 0) throw new Error('Valor deve ser positivo');
    this.#saldo += valor;
  }

  sacar(valor) {
    if (valor > this.#saldo) throw new Error('Saldo insuficiente');
    this.#saldo -= valor;
  }

  get saldo() {
    return this.#saldo;
  }
}

const conta = new ContaBancaria('Maria', 1000);
conta.depositar(500);
console.log(conta.saldo); // 1500
// console.log(conta.#saldo); // SyntaxError: campo privado inacessível

Tentar acessar #saldo diretamente resulta em erro de sintaxe, garantindo encapsulamento real.

4. Combinação de Campos Privados com Getters e Setters

A combinação de campos privados com getters e setters públicos oferece o melhor dos dois mundos: privacidade real e controle de acesso.

class Usuario {
  #senha;
  #email;

  constructor(nome, email, senha) {
    this.nome = nome;
    this.#email = email;
    this.#senha = senha;
  }

  get email() {
    return this.#email;
  }

  set email(novoEmail) {
    if (!novoEmail.includes('@')) {
      throw new Error('Email inválido');
    }
    this.#email = novoEmail;
  }

  verificarSenha(senha) {
    return this.#senha === senha;
  }

  // Getter sem setter para senha (apenas verificação)
  get temSenha() {
    return this.#senha.length > 0;
  }
}

const user = new Usuario('Ana', 'ana@email.com', '123456');
console.log(user.email); // "ana@email.com"
user.email = 'ana.novo@email.com'; // setter valida
console.log(user.email); // "ana.novo@email.com"
console.log(user.verificarSenha('123456')); // true
// user.#senha // Erro! Inacessível

5. Campos Privados vs. Convenção _ (Underscore)

Antes dos campos privados, a convenção era usar underscore (_) para indicar propriedades "privadas".

class ExemploConvencao {
  constructor() {
    this._privado = 'acessível'; // apenas convenção
  }
}

const obj = new ExemploConvencao();
console.log(obj._privado); // Funciona! Sem proteção real

class ExemploPrivado {
  #privado = 'realmente privado';

  get privado() {
    return this.#privado;
  }
}

const obj2 = new ExemploPrivado();
// console.log(obj2.#privado); // Erro!
console.log(obj2.privado); // "realmente privado" (via getter)

Diferenças principais:
- _: apenas convenção, ainda acessível externamente
- #: privacidade real, erro ao tentar acessar fora da classe

Quando usar cada abordagem:
- Use # para dados sensíveis ou que exigem encapsulamento real
- Use _ para projetos legados ou quando precisar de compatibilidade com versões antigas do Node.js

6. Casos de Uso em Node.js e React

Node.js: Modelos de dados com validação

class Produto {
  #preco;

  constructor(nome, preco) {
    this.nome = nome;
    this.#preco = preco;
  }

  get preco() {
    return this.#preco;
  }

  set preco(valor) {
    if (typeof valor !== 'number' || valor <= 0) {
      throw new Error('Preço deve ser um número positivo');
    }
    this.#preco = valor;
  }

  aplicarDesconto(percentual) {
    if (percentual < 0 || percentual > 100) {
      throw new Error('Percentual inválido');
    }
    this.#preco -= this.#preco * (percentual / 100);
  }
}

React: Gerenciamento de estado em componentes classe

class Timer extends React.Component {
  #intervalId = null;
  #contador = 0;

  constructor(props) {
    super(props);
    this.state = { segundos: 0 };
  }

  start() {
    if (this.#intervalId) return;
    this.#intervalId = setInterval(() => {
      this.#contador++;
      this.setState({ segundos: this.#contador });
    }, 1000);
  }

  stop() {
    if (this.#intervalId) {
      clearInterval(this.#intervalId);
      this.#intervalId = null;
    }
  }

  reset() {
    this.stop();
    this.#contador = 0;
    this.setState({ segundos: 0 });
  }

  render() {
    return (
      <div>
        <p>Segundos: {this.state.segundos}</p>
        <button onClick={() => this.start()}>Iniciar</button>
        <button onClick={() => this.stop()}>Parar</button>
        <button onClick={() => this.reset()}>Resetar</button>
      </div>
    );
  }

  componentWillUnmount() {
    this.stop(); // Limpeza ao desmontar
  }
}

7. Armadilhas e Boas Práticas

Getters recursivos (stack overflow)

class Problema {
  get valor() {
    return this.valor; // ERRO! Chamada recursiva infinita
  }
}

class Solucao {
  #valor;

  get valor() {
    return this.#valor; // Correto: acessa campo privado
  }
}

Setters que não alteram o valor

class ValidadorFalho {
  #dado;

  set dado(valor) {
    if (typeof valor !== 'string') {
      // Erro comum: não altera nem lança erro
      return; // Silenciosamente ignora
    }
    this.#dado = valor;
  }
}

Sempre lance erros explícitos em setters quando a validação falhar.

Compatibilidade

Campos privados # são suportados nativamente desde Node.js 12+. Para versões anteriores, use Babel ou TypeScript com configuração adequada.

// .babelrc para transpilação
{
  "presets": ["@babel/preset-env"],
  "plugins": ["@babel/plugin-proposal-class-properties"]
}

8. Conclusão e Comparação com Outras Abordagens

Getters e setters oferecem encapsulamento leve com lógica personalizada, enquanto campos privados (#) garantem privacidade real. Ambas as técnicas se complementam: campos privados protegem dados internos, e getters/setters controlam o acesso a esses dados.

Comparação com outras abordagens:

  • Object.defineProperty(): mais verboso, mas permite configuração fina (enumerable, configurable)
  • Proxy: intercepta todas as operações do objeto, útil para metaprogramação
  • Campos privados #: mais limpo e seguro para encapsulamento padrão

Dicas para projetos React modernos:
- Em componentes funcionais com hooks, use useRef para valores privados
- Em componentes classe legados, prefira campos privados # para estado interno
- Em modelos de dados (Node.js), combine campos privados com getters/setters para validação

Getters, setters e campos privados são ferramentas essenciais para escrever código JavaScript mais seguro, expressivo e bem encapsulado, tanto no backend com Node.js quanto no frontend com React.

Referências