Como Usar Classes em JavaScript: Sintaxe, Herança e Encapsulamento em Produção Já leu

Fundamentos de Classes em JavaScript Classes em JavaScript são açúcar sintático construído sobre o modelo de protótipos, introduzidas na ES6 (2015). Diferentemente de linguagens clássicas, JavaScript usa protótipos internamente, mas as classes oferecem uma sintaxe familiar e mais legível para definir objetos e suas estruturas. Compreender essa abstração é essencial para trabalhar com código moderno e profissional. Uma classe é um molde para criar objetos com propriedades e métodos. Sua sintaxe é direta: declare com , defina o para inicializar propriedades e crie métodos para comportamentos. Veja um exemplo prático: ${this.marca} ${this.modelo} acelerando para ${this.velocidade} km/h ${this.marca} ${this.modelo} freado completamente Métodos e Propriedades Estáticas Métodos estáticos pertencem à classe, não às instâncias. Úteis para funcionalidades utilitárias que não dependem de dados de uma instância específica. Use a palavra-chave : Herança e Composição A herança permite que uma classe reutilize e estenda código de outra classe. Use para herdar e para acessar o construtor da classe pai. Este padrão é fundamental

Fundamentos de Classes em JavaScript

Classes em JavaScript são açúcar sintático construído sobre o modelo de protótipos, introduzidas na ES6 (2015). Diferentemente de linguagens clássicas, JavaScript usa protótipos internamente, mas as classes oferecem uma sintaxe familiar e mais legível para definir objetos e suas estruturas. Compreender essa abstração é essencial para trabalhar com código moderno e profissional.

Uma classe é um molde para criar objetos com propriedades e métodos. Sua sintaxe é direta: declare com class, defina o constructor para inicializar propriedades e crie métodos para comportamentos. Veja um exemplo prático:

class Veiculo {
  constructor(marca, modelo) {
    this.marca = marca;
    this.modelo = modelo;
    this.velocidade = 0;
  }

  acelerar(incremento) {
    this.velocidade += incremento;
    console.log(`${this.marca} ${this.modelo} acelerando para ${this.velocidade} km/h`);
  }

  frear() {
    this.velocidade = 0;
    console.log(`${this.marca} ${this.modelo} freado completamente`);
  }
}

const carro = new Veiculo('Toyota', 'Corolla');
carro.acelerar(60);
carro.frear();

Métodos e Propriedades Estáticas

Métodos estáticos pertencem à classe, não às instâncias. Úteis para funcionalidades utilitárias que não dependem de dados de uma instância específica. Use a palavra-chave static:

class Calculadora {
  static somar(a, b) {
    return a + b;
  }

  static mediaAritmetica(numeros) {
    const soma = numeros.reduce((acc, num) => acc + num, 0);
    return soma / numeros.length;
  }
}

console.log(Calculadora.somar(5, 3)); // 8
console.log(Calculadora.mediaAritmetica([10, 20, 30])); // 20

Herança e Composição

A herança permite que uma classe reutilize e estenda código de outra classe. Use extends para herdar e super() para acessar o construtor da classe pai. Este padrão é fundamental em arquiteturas orientadas a objeto, mas use com moderação — frequentemente composição é mais flexível que herança profunda.

class Veiculo {
  constructor(marca) {
    this.marca = marca;
  }

  info() {
    return `Marca: ${this.marca}`;
  }
}

class Carro extends Veiculo {
  constructor(marca, portas) {
    super(marca);
    this.portas = portas;
  }

  info() {
    return `${super.info()} | Portas: ${this.portas}`;
  }
}

class Moto extends Veiculo {
  constructor(marca, cilindrada) {
    super(marca);
    this.cilindrada = cilindrada;
  }

  info() {
    return `${super.info()} | Cilindrada: ${this.cilindrada}cc`;
  }
}

const carro = new Carro('Honda', 4);
const moto = new Moto('Yamaha', 600);

console.log(carro.info()); // Marca: Honda | Portas: 4
console.log(moto.info());  // Marca: Yamaha | Cilindrada: 600cc

Evitando Hierarquias Profundas

Em produção, evite heranças com mais de 2 ou 3 níveis. Prefira composição: injete comportamentos através de objetos passados ao construtor. Isso torna o código mais testável e flexível:

class MotorEletrico {
  ligar() {
    return 'Motor elétrico ligado silenciosamente';
  }
}

class Bateria {
  carga = 100;
  descarregar() {
    this.carga -= 10;
    return `Bateria em ${this.carga}%`;
  }
}

class VeiculoEletrico {
  constructor(motor, bateria) {
    this.motor = motor;
    this.bateria = bateria;
  }

  ligarMotor() {
    return this.motor.ligar();
  }

  usar() {
    return this.bateria.descarregar();
  }
}

const tesla = new VeiculoEletrico(new MotorEletrico(), new Bateria());
console.log(tesla.ligarMotor());
console.log(tesla.usar());

Encapsulamento: Privacidade e Controle

O encapsulamento protege dados internos de acesso direto, expondo apenas o necessário através de uma interface pública. Em JavaScript moderno, propriedades privadas usam o prefixo #. Isso garante que apenas métodos internos à classe possam acessá-las, prevenindo modificações acidentais e facilitando refatoração segura em produção.

class ContaBancaria {
  #saldo = 0;
  #historico = [];

  constructor(titular, saldoInicial = 0) {
    this.titular = titular;
    this.#saldo = saldoInicial;
    this.#historico.push({ operacao: 'Abertura', valor: saldoInicial, data: new Date() });
  }

  depositar(valor) {
    if (valor <= 0) {
      throw new Error('Valor deve ser positivo');
    }
    this.#saldo += valor;
    this.#historico.push({ operacao: 'Depósito', valor, data: new Date() });
    return `Depósito de R$${valor} realizado. Saldo: R$${this.#saldo}`;
  }

  sacar(valor) {
    if (valor > this.#saldo) {
      throw new Error('Saldo insuficiente');
    }
    this.#saldo -= valor;
    this.#historico.push({ operacao: 'Saque', valor, data: new Date() });
    return `Saque de R$${valor} realizado. Saldo: R$${this.#saldo}`;
  }

  obterSaldo() {
    return this.#saldo;
  }

  obterHistorico() {
    return [...this.#historico];
  }
}

const conta = new ContaBancaria('João', 1000);
console.log(conta.depositar(500));
console.log(conta.sacar(200));
console.log(conta.obterSaldo());
// conta.#saldo = -9999; // Erro: propriedade privada é inacessível

Getters e Setters

Getters e setters permitem validação e lógica customizada ao acessar propriedades, mantendo a sintaxe de propriedades simples. Essencial para classes em produção que precisam de regras de negócio:

class Usuario {
  #email;
  #idade;

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

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

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

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

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

const usuario = new Usuario('Maria', 'maria@email.com', 28);
console.log(usuario.email);
usuario.idade = 29;
console.log(usuario.idade);
// usuario.email = 'invalido'; // Erro: Email inválido

Boas Práticas em Produção

Em ambientes reais, siga estas diretrizes: (1) Use encapsulamento por padrão — propriedades privadas protegem sua arquitetura; (2) Prefira composição a herança profunda — é mais simples de entender e testar; (3) Valide dados nos setters e construtores — evita estados inválidos; (4) Documente com JSDoc — facilita manutenção futura.

/**
 * Classe que representa um Produto no sistema de e-commerce
 * @class Produto
 * @param {string} nome - Nome do produto
 * @param {number} preco - Preço em reais
 * @param {number} estoque - Quantidade em estoque
 */
class Produto {
  #preco;
  #estoque;

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

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

  set preco(valor) {
    if (valor <= 0) throw new Error('Preço deve ser positivo');
    this.#preco = valor;
  }

  /**
   * Reduz estoque após venda
   * @param {number} quantidade
   * @returns {boolean} Sucesso da operação
   */
  vender(quantidade) {
    if (quantidade > this.#estoque) return false;
    this.#estoque -= quantidade;
    return true;
  }

  obterEstoque() {
    return this.#estoque;
  }
}

const produto = new Produto('Notebook', 3000, 5);
produto.vender(2);
console.log(produto.obterEstoque()); // 3

Conclusão

Classes em JavaScript oferecem uma abordagem profissional e estruturada para organizar código. O domínio de herança com extends e super, encapsulamento com propriedades privadas e getters/setters com validação é indispensável em projetos reais. Lembre-se: prefira composição quando possível, sempre valide dados e documente sua código — essas práticas garantem manutenibilidade a longo prazo.

Referências


Artigos relacionados