Classes em TypeScript: visibilidade e modificadores
1. Introdução à Visibilidade em Classes TypeScript
Modificadores de acesso são palavras-chave que controlam onde propriedades e métodos de uma classe podem ser acessados. Em JavaScript, a privacidade sempre foi uma convenção — desenvolvedores usavam _ como prefixo para indicar membros "privados", mas nada impedia o acesso externo. TypeScript introduz controle real em tempo de compilação, garantindo que violações de encapsulamento sejam detectadas antes da execução.
TypeScript oferece três modificadores de visibilidade: public (acessível em qualquer lugar), protected (acessível na classe e subclasses) e private (acessível apenas na própria classe). Vamos explorar cada um em detalhes.
2. O Modificador public – Comportamento Padrão
public é o modificador implícito em TypeScript. Se nenhum modificador for especificado, o membro é considerado público. Isso significa que pode ser acessado de dentro da classe, de subclasses e de qualquer código externo.
class LoggerService {
log(message: string): void {
console.log(`[LOG]: ${message}`);
}
formatMessage(text: string): string {
return `[${new Date().toISOString()}] ${text}`;
}
}
const logger = new LoggerService();
logger.log("Serviço iniciado"); // Acesso externo permitido
logger.formatMessage("teste"); // Também permitido
Embora útil, expor tudo como público quebra o encapsulamento. A boa prática é tornar público apenas o que faz parte da API da classe.
3. O Modificador private – Encapsulamento Restrito
Membros private só podem ser acessados dentro da própria classe que os declara. Nem mesmo subclasses têm acesso.
class Account {
private balance: number;
private transactionHistory: string[] = [];
constructor(initialBalance: number) {
this.balance = initialBalance;
}
private addTransaction(type: string, amount: number): void {
this.transactionHistory.push(`${type}: R$${amount.toFixed(2)}`);
}
deposit(amount: number): void {
this.balance += amount;
this.addTransaction("Depósito", amount);
}
getBalance(): number {
return this.balance;
}
}
const account = new Account(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
// account.balance; // Erro: 'balance' é privado
// account.addTransaction("Teste", 100); // Erro: método privado
Diferença entre private do TypeScript e # do JavaScript: O modificador private do TypeScript é uma verificação apenas em tempo de compilação. O JavaScript gerado não mantém essa restrição. Já o campo # (privado nativo do JavaScript) oferece encapsulamento real em tempo de execução, mas não é intercambiável com private do TypeScript.
class ModernAccount {
#balance: number; // Campo privado nativo (ES2020+)
constructor(initialBalance: number) {
this.#balance = initialBalance;
}
getBalance(): number {
return this.#balance;
}
}
4. O Modificador protected – Visibilidade em Hierarquias
Membros protected são acessíveis na classe base e em qualquer subclasse, mas não fora da cadeia de herança.
abstract class Shape {
protected calculateAreaBase(): number {
return 0; // Implementação base
}
abstract getArea(): number;
}
class Circle extends Shape {
constructor(private radius: number) {
super();
}
getArea(): number {
return Math.PI * this.radius * this.radius;
}
logBaseCalculation(): void {
console.log(`Área base: ${this.calculateAreaBase()}`); // Acesso permitido
}
}
class Square extends Shape {
constructor(private side: number) {
super();
}
getArea(): number {
return this.side * this.side;
}
}
const circle = new Circle(5);
circle.getArea(); // OK
// circle.calculateAreaBase(); // Erro: 'calculateAreaBase' é protegido
5. Modificadores de Parâmetros no Construtor
TypeScript oferece uma sintaxe reduzida para declarar e inicializar propriedades diretamente no construtor, combinando declaração, tipo e modificador de visibilidade.
class User {
constructor(
public name: string,
private readonly id: string,
protected email: string
) {
// As propriedades já estão declaradas e inicializadas
}
displayInfo(): void {
console.log(`Usuário: ${this.name}, ID: ${this.id}`);
}
}
const user = new User("Alice", "uuid-123", "alice@email.com");
console.log(user.name); // OK
// console.log(user.id); // Erro: 'id' é privado
// console.log(user.email); // Erro: 'email' é protegido
Essa sintaxe elimina a repetição de declarar a propriedade e depois atribuí-la no corpo do construtor.
6. Modificador readonly – Imutabilidade de Propriedades
O modificador readonly impede que uma propriedade seja reatribuída após a inicialização. Pode ser combinado com qualquer modificador de visibilidade.
class AppConfig {
constructor(
public readonly appName: string,
private readonly apiKey: string,
protected readonly version: string = "1.0.0"
) {}
getApiKey(): string {
return this.apiKey;
}
}
const config = new AppConfig("MyApp", "sk-12345");
console.log(config.appName); // "MyApp"
// config.appName = "NewName"; // Erro: 'appName' é readonly
console.log(config.getApiKey()); // "sk-12345"
Propriedades readonly podem ser atribuídas apenas na declaração ou dentro do construtor. Qualquer tentativa de modificação posterior gera erro de compilação.
7. Modificador static – Membros de Classe vs. Membros de Instância
Membros static pertencem à classe, não a instâncias individuais. São acessados através do nome da classe e podem combinar com qualquer modificador de visibilidade.
class InstanceCounter {
private static instanceCount = 0;
public static readonly MAX_INSTANCES = 100;
constructor() {
if (InstanceCounter.instanceCount >= InstanceCounter.MAX_INSTANCES) {
throw new Error("Limite máximo de instâncias atingido");
}
InstanceCounter.incrementCount();
}
private static incrementCount(): void {
InstanceCounter.instanceCount++;
}
static getCurrentCount(): number {
return InstanceCounter.instanceCount;
}
}
const obj1 = new InstanceCounter();
const obj2 = new InstanceCounter();
console.log(InstanceCounter.getCurrentCount()); // 2
console.log(InstanceCounter.MAX_INSTANCES); // 100
// InstanceCounter.incrementCount(); // Erro: método privado
Membros estáticos privados são úteis para contadores, caches ou configurações globais que não devem ser expostos.
8. Considerações Finais e Boas Práticas
Quando usar cada modificador:
public: Para a API pública da classe — métodos e propriedades que outros módulos precisam consumir.private: Para detalhes internos de implementação — dados sensíveis, lógica auxiliar, estado que não deve ser exposto.protected: Quando subclasses precisam de acesso, mas o mundo externo não. Ideal para frameworks e bibliotecas que esperam extensão.
Encapsulamento na herança: private impede acesso mesmo em subclasses; protected permite. Escolha protected apenas se a hierarquia realmente precisar desse acesso.
Limitação importante: TypeScript não emite verificações de visibilidade no JavaScript gerado. Se o código for transpilado para ES5, membros private e protected serão simples propriedades acessíveis em tempo de execução. Para proteção real em runtime, use campos privados nativos (#).
Combinações poderosas: private readonly para valores imutáveis internos, protected static para utilitários compartilhados em hierarquias, public static readonly para constantes da classe.
class BestPractices {
// API pública
public execute(): void {
this.validate();
this.process();
}
// Detalhes internos
private validate(): boolean {
return true;
}
// Acessível por subclasses
protected process(): void {
console.log("Processando...");
}
// Constante pública
public static readonly VERSION = "2.0.0";
// Cache interno
private static cache = new Map<string, unknown>();
}
Dominar os modificadores de visibilidade e acesso em TypeScript permite criar APIs mais limpas, seguras e expressivas, aproveitando ao máximo o sistema de tipos da linguagem.
Referências
- TypeScript Handbook: Classes — Documentação oficial sobre classes, incluindo modificadores de visibilidade e parâmetros no construtor
- TypeScript: Playground - Class Members — Ambiente interativo para testar classes e modificadores em tempo real
- TypeScript Deep Dive: Classes — Guia abrangente sobre classes TypeScript com exemplos práticos de encapsulamento
- MDN Web Docs: Private class fields — Documentação sobre campos privados nativos do JavaScript (
#) e diferenças com TypeScript - TypeScript: Release Notes - 3.8 (Private Fields) — Notas de lançamento sobre suporte a campos privados nativos no TypeScript 3.8
- Refactoring Guru: Encapsulation in TypeScript — Padrões de encapsulamento aplicados em TypeScript com exemplos de modificadores de acesso