Abstract classes e métodos abstratos
1. Introdução às Classes Abstratas
Classes abstratas são um dos pilares da programação orientada a objetos em TypeScript, servindo como modelos base que não podem ser instanciados diretamente. Elas representam conceitos genéricos que precisam de especialização para serem úteis, funcionando como contratos parciais que combinam implementação concreta com definições abstratas.
A diferença fundamental entre classes concretas e abstratas é que estas últimas podem declarar métodos sem implementação, deixando a responsabilidade para as subclasses. Você deve utilizar classes abstratas quando deseja compartilhar lógica comum entre classes relacionadas, mas ainda precisa garantir que certos comportamentos sejam implementados de forma específica. Diferentemente das interfaces, classes abstratas podem conter implementação parcial e estado interno.
2. Declarando Classes e Métodos Abstratos
A sintaxe para declarar classes e métodos abstratos utiliza a palavra-chave abstract. Métodos abstratos são declarados sem corpo, apenas com sua assinatura:
abstract class Animal {
constructor(protected nome: string) {}
// Método concreto - possui implementação
mover(): void {
console.log(`${this.nome} está se movendo`);
}
// Método abstrato - sem implementação
abstract emitirSom(): void;
}
Regras importantes:
- Métodos abstratos não possuem implementação (sem chaves {})
- Classes que possuem pelo menos um método abstrato devem ser declaradas como abstract
- Não é possível instanciar uma classe abstrata diretamente
3. Herança e Implementação em Subclasses
Subclasses concretas devem implementar todos os métodos abstratos da classe base. O TypeScript verifica isso em tempo de compilação:
class Cachorro extends Animal {
constructor(nome: string) {
super(nome);
}
// Implementação obrigatória do método abstrato
emitirSom(): void {
console.log(`${this.nome} late: Au au!`);
}
}
class Gato extends Animal {
constructor(nome: string) {
super(nome);
}
emitirSom(): void {
console.log(`${this.nome} mia: Miau!`);
}
}
const rex = new Cachorro("Rex");
rex.emitirSom(); // "Rex late: Au au!"
rex.mover(); // "Rex está se movendo"
Se uma subclasse não implementar um método abstrato, o TypeScript emitirá um erro: Non-abstract class 'Gato' does not implement inherited abstract member 'emitirSom' from class 'Animal'.
4. Propriedades e Construtores em Classes Abstratas
Classes abstratas podem declarar propriedades abstratas, que também precisam ser implementadas pelas subclasses:
abstract class Veiculo {
abstract readonly rodas: number;
protected velocidade: number = 0;
constructor(protected modelo: string) {}
abstract acelerar(): void;
frear(): void {
this.velocidade = 0;
console.log(`${this.modelo} parou`);
}
}
class Carro extends Veiculo {
readonly rodas: number = 4;
constructor(modelo: string) {
super(modelo);
}
acelerar(): void {
this.velocidade += 20;
console.log(`${this.modelo} acelerou para ${this.velocidade} km/h`);
}
}
Construtores em classes abstratas seguem regras específicas:
- Podem ter parâmetros e inicializar propriedades
- São chamados via super() nas subclasses
- Não podem ser chamados diretamente com new
5. Classes Abstratas vs Interfaces
A escolha entre classes abstratas e interfaces depende do contexto:
Interfaces são ideais para definir contratos puros, sem implementação:
interface Imprimivel {
imprimir(): void;
obterTamanho(): number;
}
Classes abstratas são melhores quando há lógica compartilhada:
abstract class Documento {
protected conteudo: string = '';
abstract imprimir(): void;
carregarConteudo(texto: string): void {
this.conteudo = texto;
console.log('Conteúdo carregado');
}
}
Use classes abstratas quando:
- Precisa compartilhar implementação entre classes relacionadas
- Deseja proteger membros com protected
- Requer estado interno (propriedades com valores)
Use interfaces quando:
- Precisa de herança múltipla (uma classe pode implementar várias interfaces)
- Apenas define a forma dos dados, sem comportamento
- Trabalha com tipos que não têm relação de hierarquia
6. Padrões de Design com Classes Abstratas
Template Method Pattern: Define o esqueleto de um algoritmo, deixando etapas específicas para subclasses:
abstract class ProcessadorDeDados {
processar(): void {
this.carregarDados();
this.transformarDados();
this.salvarResultados();
this.notificar();
}
private carregarDados(): void {
console.log("Carregando dados...");
}
protected abstract transformarDados(): void;
private salvarResultados(): void {
console.log("Salvando resultados...");
}
protected abstract notificar(): void;
}
class ProcessadorCSV extends ProcessadorDeDados {
protected transformarDados(): void {
console.log("Transformando dados CSV...");
}
protected notificar(): void {
console.log("Enviando e-mail com resultados CSV");
}
}
Factory Method Pattern: Cria objetos através de métodos abstratos:
abstract class CriadorDeNotificacao {
abstract criarNotificacao(): Notificacao;
enviarMensagem(mensagem: string): void {
const notificacao = this.criarNotificacao();
notificacao.enviar(mensagem);
}
}
interface Notificacao {
enviar(mensagem: string): void;
}
class NotificacaoEmail implements Notificacao {
enviar(mensagem: string): void {
console.log(`Email enviado: ${mensagem}`);
}
}
class CriadorEmail extends CriadorDeNotificacao {
criarNotificacao(): Notificacao {
return new NotificacaoEmail();
}
}
7. Boas Práticas e Limitações
Boas práticas:
- Mantenha hierarquias rasas (no máximo 2-3 níveis de herança)
- Use
protectedpara métodos e propriedades que subclasses precisam acessar - Documente claramente o propósito de cada método abstrato
abstract class FormaGeometrica {
protected abstract readonly nome: string;
public abstract calcularArea(): number;
exibirInformacoes(): void {
console.log(`Forma: ${this.nome}, Área: ${this.calcularArea()}`);
}
}
Limitações importantes:
- Não é possível instanciar classes abstratas:
new Animal()gera erro - TypeScript não suporta herança múltipla de classes (uma classe só pode estender uma classe abstrata)
- Métodos abstratos não podem ser
private(devem ser acessíveis por subclasses)
Testes unitários: Para testar classes abstratas, crie uma subclasse de teste fictícia (stub) que implemente os métodos abstratos:
class AnimalTeste extends Animal {
emitirSom(): void {
console.log("Som de teste");
}
}
// Agora é possível testar a lógica concreta
const animalTeste = new AnimalTeste("Teste");
animalTeste.mover(); // Testa implementação da classe base
Classes abstratas são ferramentas poderosas para criar hierarquias coesas e reutilizáveis, especialmente quando combinadas com padrões de design como Template Method. Use-as com moderação e sempre avalie se uma interface não seria mais adequada para seu caso de uso.
Referências
- TypeScript Handbook: Classes - Abstract Classes — Documentação oficial da Microsoft sobre classes abstratas em TypeScript, com exemplos e regras de sintaxe
- TypeScript Deep Dive: Abstract Classes — Guia abrangente sobre classes abstratas, incluindo comparação com interfaces e casos de uso avançados
- Refactoring Guru: Template Method Pattern in TypeScript — Explicação detalhada do padrão Template Method com implementação em TypeScript usando classes abstratas
- TypeScript Playground: Abstract Classes Examples — Ambiente interativo oficial para experimentar classes abstratas com exemplos prontos
- DigitalOcean: Understanding Abstract Classes in TypeScript — Tutorial prático cobrindo desde conceitos básicos até padrões avançados com classes abstratas
- LogRocket: Abstract classes vs interfaces in TypeScript — Análise comparativa detalhada entre classes abstratas e interfaces, com exemplos do mundo real