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.