Dominando Classes em TypeScript: Modificadores, Readonly e Parameter Properties em Projetos Reais Já leu

Introdução aos Modificadores de Acesso em TypeScript Os modificadores de acesso são um dos pilares da Programação Orientada a Objetos e determinam a visibilidade de propriedades e métodos dentro de uma classe. Em TypeScript, você tem controle total sobre o que fica privado, protegido ou público, permitindo criar abstrações robustas e interfaces seguras. Diferentemente de JavaScript puro, TypeScript oferece três modificadores principais: , e , além da palavra-chave que trabalha em conjunto com eles. Compreender quando usar cada modificador é fundamental para escrever código mantível e seguro. Um modificador bem escolhido evita que dados sensíveis sejam acessados diretamente, força o uso de métodos controlados e deixa clara a intenção do código para quem o lê. Vou guiá-lo através de cada um deles com exemplos práticos que você pode usar no seu dia a dia. Public, Private e Protected: Controlando Visibilidade O modificador Public O modificador é o padrão em TypeScript. Qualquer propriedade ou método declarado como (ou sem modificador explícito)

Introdução aos Modificadores de Acesso em TypeScript

Os modificadores de acesso são um dos pilares da Programação Orientada a Objetos e determinam a visibilidade de propriedades e métodos dentro de uma classe. Em TypeScript, você tem controle total sobre o que fica privado, protegido ou público, permitindo criar abstrações robustas e interfaces seguras. Diferentemente de JavaScript puro, TypeScript oferece três modificadores principais: public, private e protected, além da palavra-chave readonly que trabalha em conjunto com eles.

Compreender quando usar cada modificador é fundamental para escrever código mantível e seguro. Um modificador bem escolhido evita que dados sensíveis sejam acessados diretamente, força o uso de métodos controlados e deixa clara a intenção do código para quem o lê. Vou guiá-lo através de cada um deles com exemplos práticos que você pode usar no seu dia a dia.

Public, Private e Protected: Controlando Visibilidade

O modificador Public

O modificador public é o padrão em TypeScript. Qualquer propriedade ou método declarado como public (ou sem modificador explícito) pode ser acessado de qualquer lugar: dentro da classe, fora dela, em subclasses e em qualquer código cliente. Use public quando o membro faz parte da interface pública da sua classe.

class Usuario {
  public nome: string;
  public email: string;

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

  public exibirPerfil(): string {
    return `${this.nome} (${this.email})`;
  }
}

const usuario = new Usuario("Ana Silva", "ana@example.com");
console.log(usuario.nome); // "Ana Silva" - sem problemas
console.log(usuario.exibirPerfil()); // "Ana Silva (ana@example.com)"

O modificador Private

O modificador private restringe o acesso a um membro apenas dentro da classe onde foi declarado. Nenhuma subclasse pode acessar membros privados, e código externo definitivamente não pode. Use private para dados internos que não devem ser expostos diretamente, como credenciais ou estado sensível.

class ContaBancaria {
  public titular: string;
  private saldo: number; // Apenas leitura e escrita dentro da classe

  constructor(titular: string, saldoInicial: number) {
    this.titular = titular;
    this.saldo = saldoInicial;
  }

  public depositar(valor: number): void {
    if (valor > 0) {
      this.saldo += valor;
      console.log(`Depósito de R$ ${valor} realizado.`);
    }
  }

  public sacar(valor: number): boolean {
    if (valor > 0 && valor <= this.saldo) {
      this.saldo -= valor;
      console.log(`Saque de R$ ${valor} realizado.`);
      return true;
    }
    return false;
  }

  public obterSaldo(): number {
    return this.saldo; // Acesso controlado via método
  }
}

const conta = new ContaBancaria("João", 1000);
conta.depositar(500);
console.log(conta.obterSaldo()); // 1500
// conta.saldo = 99999; // ERRO: Property 'saldo' is private

O modificador Protected

O modificador protected é um meio termo entre public e private. Membros protegidos podem ser acessados dentro da classe e também dentro de subclasses, mas não fora delas. Use protected quando quiser criar uma hierarquia de classes e compartilhar dados entre classe base e filhas.

class Animal {
  public nome: string;
  protected energia: number; // Acessível em subclasses

  constructor(nome: string) {
    this.nome = nome;
    this.energia = 100;
  }

  protected gastarEnergia(quantidade: number): void {
    this.energia = Math.max(0, this.energia - quantidade);
  }

  public status(): string {
    return `${this.nome} tem ${this.energia} de energia.`;
  }
}

class Cachorro extends Animal {
  public correr(): void {
    console.log(`${this.nome} está correndo!`);
    this.gastarEnergia(20); // Pode acessar protected
  }
}

const dog = new Cachorro("Rex");
console.log(dog.status()); // "Rex tem 100 de energia."
dog.correr();
console.log(dog.status()); // "Rex tem 80 de energia."
// dog.energia = 50; // ERRO: Property 'energia' is protected

Readonly: Imutabilidade Garantida

Entendendo a Imutabilidade com Readonly

O modificador readonly (que pode ser combinado com os anteriores) faz com que uma propriedade não possa ser modificada após sua inicialização. Diferentemente de private, uma propriedade readonly pode ser public e ser lida por qualquer um, mas sua escrita é bloqueada. Isso é extremamente útil para valores que não devem mudar, como identificadores ou configurações.

A grande vantagem do readonly é a segurança em tempo de compilação. O TypeScript impedirá que você tente reatribuir o valor, evitando bugs sutis que passariam despercebidos em JavaScript puro.

class Produto {
  readonly id: number;
  public nome: string;
  readonly dataCriacao: Date;

  constructor(id: number, nome: string) {
    this.id = id;
    this.nome = nome;
    this.dataCriacao = new Date();
  }

  public atualizarNome(novoNome: string): void {
    this.nome = novoNome;
  }
}

const produto = new Produto(1, "Laptop");
console.log(produto.id); // 1 - leitura permitida
console.log(produto.dataCriacao); // Date object
produto.atualizarNome("Laptop Dell"); // funciona
// produto.id = 2; // ERRO: Cannot assign to 'id' because it is a read-only property

Readonly com Modificadores de Acesso

Você pode combinar readonly com public, private ou protected para criar diferentes níveis de restrição. Uma propriedade private readonly é um membro totalmente imutável e inacessível, enquanto public readonly permite leitura mas impede modificação externa.

class Configuracao {
  public readonly versao: string = "1.0.0";
  private readonly chaveSecreta: string;
  protected readonly timeout: number;

  constructor(chaveSecreta: string) {
    this.chaveSecreta = chaveSecreta; // Inicialização é permitida
    this.timeout = 30000;
  }

  public obterVersao(): string {
    return this.versao; // Leitura sem problemas
  }

  private obterChave(): string {
    return this.chaveSecreta; // Acessível apenas internamente
  }
}

const config = new Configuracao("abc123xyz");
console.log(config.versao); // "1.0.0"
// config.versao = "2.0.0"; // ERRO: Cannot assign to 'versao'

Parameter Properties: Sintaxe Reduzida

O que São Parameter Properties

Parameter Properties (ou propriedades de parâmetro) são um açúcar sintático do TypeScript que permite declarar, inicializar e atribuir modificadores a propriedades de classe diretamente na lista de parâmetros do construtor. Em vez de declarar a propriedade, depois declará-la novamente no construtor e atribuir o parâmetro, você faz tudo em uma linha. Isso reduz boilerplate significativamente.

// Forma tradicional (verbose)
class Pessoa {
  private nome: string;
  private idade: number;

  constructor(nome: string, idade: number) {
    this.nome = nome;
    this.idade = idade;
  }
}

// Com Parameter Properties (concisa)
class Pessoa {
  constructor(private nome: string, private idade: number) {}
}

Sintaxe e Exemplos Práticos

A sintaxe é simples: coloque o modificador de acesso (e opcionalmente readonly) antes do parâmetro no construtor. TypeScript automaticamente criará a propriedade com aquele modificador e atribuirá o valor do parâmetro a ela. Isso funciona com public, private, protected e readonly.

class Estudante {
  constructor(
    public nome: string,
    private matricula: string,
    protected notas: number[],
    readonly dataNascimento: Date
  ) {}

  public exibirInfo(): void {
    console.log(`${this.nome} - Matrícula: ${this.matricula}`);
  }

  public adicionarNota(nota: number): void {
    this.notas.push(nota);
  }

  public obterMedia(): number {
    return this.notas.reduce((a, b) => a + b, 0) / this.notas.length;
  }
}

const aluno = new Estudante(
  "Carlos",
  "2024001",
  [8, 7.5, 9],
  new Date("2005-03-15")
);

console.log(aluno.nome); // "Carlos" - public é acessível
aluno.exibirInfo(); // "Carlos - Matrícula: 2024001"
aluno.adicionarNota(8.5);
console.log(aluno.obterMedia()); // 8.25
// aluno.matricula = "2024002"; // ERRO: private
// aluno.dataNascimento = new Date(); // ERRO: readonly

Combinando Parameter Properties com Métodos

Parameter Properties brilham quando você precisa criar classes simples com comportamento. Compare a clareza: uma classe que poderia ter dez linhas fica com três. O código fica mais legível porque você vê imediatamente quais são as dependências da classe e seus modificadores.

class Logger {
  constructor(
    private timestamp: boolean,
    private nivelMinimo: "debug" | "info" | "warn" | "error",
    readonly nomeServico: string
  ) {}

  public registrar(nivel: "debug" | "info" | "warn" | "error", mensagem: string): void {
    const niveis = { debug: 0, info: 1, warn: 2, error: 3 };

    if (niveis[nivel] >= niveis[this.nivelMinimo]) {
      const prefixo = this.timestamp ? `[${new Date().toISOString()}]` : "";
      const sufixo = `[${this.nomeServico}]`;
      console.log(`${prefixo} ${nivel.toUpperCase()} ${sufixo}: ${mensagem}`);
    }
  }
}

const log = new Logger(true, "info", "ApiServer");
log.registrar("info", "Servidor iniciado");
log.registrar("debug", "Esta mensagem será ignorada");
log.registrar("error", "Erro crítico detectado");

Conclusão

Você aprendeu três conceitos fundamentais que transformam a qualidade do seu código TypeScript. Primeiro, os modificadores public, private e protected permitem controlar a visibilidade e criar abstrações seguras — use private para ocultar detalhes de implementação, protected para compartilhar entre hierarquias e public apenas para o que deve ser exposto. Segundo, readonly oferece imutabilidade em tempo de compilação, prevenindo reatribuições acidentais de valores que não devem mudar, e combina-se perfeitamente com os outros modificadores. Terceiro, Parameter Properties eliminam boilerplate repetitivo, deixando seu construtor limpo e declarativo — declare tudo em uma linha em vez de três.

A verdadeira maestria vem de saber quando usar cada um. Uma classe bem estruturada com modificadores apropriados é um contrato claro entre você e quem usa seu código: ela diz explicitamente "isso é interno, não mexe", "isso pode ler mas não mudar" e "isso é parte da interface pública". Comece aplicando esses conceitos em seus projetos e você verá como bugs diminuem e a manutenção fica mais tranquila.

Referências


Artigos relacionados