TypeScript

Como Usar Abstract Classes e Métodos Abstratos em TypeScript em Produção

8 min de leitura

Como Usar Abstract Classes e Métodos Abstratos em TypeScript em Produção

O que são Classes Abstratas em TypeScript? Uma classe abstrata é um modelo ou contrato que define a estrutura e o comportamento que suas classes filhas devem seguir. Diferentemente de uma classe comum, uma classe abstrata não pode ser instanciada diretamente. Seu propósito é servir como uma base para outras classes herdarem seus métodos e propriedades. Em TypeScript, você declara uma classe abstrata usando a palavra-chave . Isso significa que a classe existe para ser estendida, não para criar objetos dela mesma. É um padrão poderoso para impor consistência em projetos grandes, onde múltiplas classes precisam seguir a mesma interface de implementação. Métodos Abstratos: Definindo Contratos O que é um Método Abstrato? Um método abstrato é um método declarado dentro de uma classe abstrata sem implementação. Ele define apenas a assinatura (nome, parâmetros e tipo de retorno), deixando a implementação real para as classes filhas. Isso garante que toda classe que herdar da abstrata obrigatoriamente implemente esse método. A vantagem

<h2>O que são Classes Abstratas em TypeScript?</h2>

<p>Uma classe abstrata é um modelo ou contrato que define a estrutura e o comportamento que suas classes filhas devem seguir. Diferentemente de uma classe comum, uma classe abstrata <strong>não pode ser instanciada diretamente</strong>. Seu propósito é servir como uma base para outras classes herdarem seus métodos e propriedades.</p>

<p>Em TypeScript, você declara uma classe abstrata usando a palavra-chave <code>abstract</code>. Isso significa que a classe existe para ser estendida, não para criar objetos dela mesma. É um padrão poderoso para impor consistência em projetos grandes, onde múltiplas classes precisam seguir a mesma interface de implementação.</p>

<h2>Métodos Abstratos: Definindo Contratos</h2>

<h3>O que é um Método Abstrato?</h3>

<p>Um método abstrato é um método declarado dentro de uma classe abstrata <strong>sem implementação</strong>. Ele define apenas a assinatura (nome, parâmetros e tipo de retorno), deixando a implementação real para as classes filhas. Isso garante que toda classe que herdar da abstrata <strong>obrigatoriamente implemente</strong> esse método.</p>

<p>A vantagem principal é que você força o contrato: qualquer desenvolvedor que use sua classe abstrata saberá exatamente quais métodos precisam ser implementados, evitando esquecimentos ou inconsistências.</p>

<h3>Sintaxe Básica</h3>

<pre><code class="language-typescript">abstract class Animal {

abstract fazerSom(): void;

abstract obterEspecie(): string;

}</code></pre>

<p>Aqui, qualquer classe que herdar de <code>Animal</code> <strong>deve</strong> implementar <code>fazerSom()</code> e <code>obterEspecie()</code>. Se não implementar, o TypeScript lançará um erro em tempo de compilação.</p>

<h2>Exemplo Prático: Um Sistema de Pagamentos</h2>

<p>Vou mostrar um caso real que você provavelmente enfrentará na carreira: um sistema que processa diferentes tipos de pagamento (cartão, boleto, transferência bancária).</p>

<pre><code class="language-typescript">abstract class ProcessadorPagamento {

private codigoTransacao: string;

constructor() {

this.codigoTransacao = Math.random().toString(36).substring(7);

}

// Método abstrato - deve ser implementado pelas classes filhas

abstract validarDados(dados: object): boolean;

// Outro método abstrato

abstract processar(valor: number): Promise&lt;boolean&gt;;

// Método concreto - é opcional implementar em subclasses

obterCodigoTransacao(): string {

return this.codigoTransacao;

}

// Método concreto que usa lógica compartilhada

registrarLog(mensagem: string): void {

const timestamp = new Date().toISOString();

console.log([${timestamp}] ${mensagem});

}

}

class PagamentoCartao extends ProcessadorPagamento {

validarDados(dados: { numero: string; cvv: string }): boolean {

return dados.numero.length === 16 &amp;&amp; dados.cvv.length === 3;

}

async processar(valor: number): Promise&lt;boolean&gt; {

this.registrarLog(Processando pagamento de R$ ${valor} por cartão);

// Simula validação com banco

return new Promise((resolve) =&gt; {

setTimeout(() =&gt; {

const sucesso = valor &gt; 0 &amp;&amp; valor &lt; 100000;

this.registrarLog(

Cartão: ${sucesso ? &#039;Aprovado&#039; : &#039;Recusado&#039;} - TX: ${this.obterCodigoTransacao()}

);

resolve(sucesso);

}, 1000);

});

}

}

class PagamentoBoleto extends ProcessadorPagamento {

validarDados(dados: { codigoBarras: string }): boolean {

return dados.codigoBarras.length === 47;

}

async processar(valor: number): Promise&lt;boolean&gt; {

this.registrarLog(Processando boleto de R$ ${valor});

// Boletos têm processamento mais lento

return new Promise((resolve) =&gt; {

setTimeout(() =&gt; {

this.registrarLog(

Boleto: Registrado para compensação - TX: ${this.obterCodigoTransacao()}

);

resolve(true);

}, 3000);

});

}

}

class PagamentoTransferencia extends ProcessadorPagamento {

validarDados(dados: { banco: string; conta: string }): boolean {

return dados.banco.length &gt; 0 &amp;&amp; dados.conta.length &gt;= 8;

}

async processar(valor: number): Promise&lt;boolean&gt; {

this.registrarLog(Processando transferência de R$ ${valor});

return new Promise((resolve) =&gt; {

setTimeout(() =&gt; {

this.registrarLog(

Transferência: Iniciada - TX: ${this.obterCodigoTransacao()}

);

resolve(true);

}, 500);

});

}

}

// Usando as classes

async function processarCheckout() {

const pagamentoCartao = new PagamentoCartao();

const dadosCartao = { numero: &#039;1234567890123456&#039;, cvv: &#039;123&#039; };

if (pagamentoCartao.validarDados(dadosCartao)) {

const aprovado = await pagamentoCartao.processar(150.00);

console.log(Resultado: ${aprovado ? &#039;Pagamento realizado&#039; : &#039;Falha&#039;}\n);

}

const pagamentoBoleto = new PagamentoBoleto();

const dadosBoleto = { codigoBarras: &#039;12345678901234567890123456789012345678901234567&#039; };

if (pagamentoBoleto.validarDados(dadosBoleto)) {

const processado = await pagamentoBoleto.processar(250.00);

console.log(Resultado: ${processado ? &#039;Boleto registrado&#039; : &#039;Falha&#039;}\n);

}

}

processarCheckout();</code></pre>

<p>Observe que:</p>

<ul>

<li>As três classes <strong>implementam obrigatoriamente</strong> os métodos abstratos <code>validarDados()</code> e <code>processar()</code></li>

<li>Todas herdam o método <code>registrarLog()</code> sem precisar reimplementá-lo</li>

<li>Você nunca pode fazer <code>new ProcessadorPagamento()</code> — isso vai gerar erro</li>

</ul>

<h2>Quando Usar Classes e Métodos Abstratos</h2>

<h3>Cenários Ideais</h3>

<p>Use classes abstratas quando você tem <strong>uma hierarquia de classes relacionadas</strong> que compartilham comportamento comum, mas têm implementações diferentes. Os padrões mais comuns são:</p>

<ol>

<li><strong>Factory Pattern</strong>: Uma classe abstrata define a interface, subclasses criam implementações específicas</li>

<li><strong>Template Method</strong>: A classe abstrata define o &quot;esqueleto&quot; do algoritmo, subclasses implementam detalhes</li>

<li><strong>Strategy Pattern</strong>: Diferentes estratégias implementam a mesma interface abstrata</li>

</ol>

<h3>O que NÃO Fazer</h3>

<p>Não use classes abstratas para simples herança de propriedades. Se você apenas quer compartilhar dados entre classes, uma classe regular é suficiente. Classes abstratas devem impor um <strong>contrato de implementação</strong>, não apenas reutilizar código.</p>

<pre><code class="language-typescript"></code></pre>

<h2>Propriedades Abstratas: Um Detalhe Importante</h2>

<p>Além de métodos, você também pode definir propriedades abstratas em TypeScript. Isso força as subclasses a declarar essas propriedades:</p>

<pre><code class="language-typescript">abstract class Usuario {

abstract id: number;

abstract nome: string;

abstract email: string;

abstract validarEmail(): boolean;

}

class UsuarioAdmin extends Usuario {

id: number;

nome: string;

email: string;

nivelAcesso: number = 10;

constructor(id: number, nome: string, email: string) {

super();

this.id = id;

this.nome = nome;

this.email = email;

}

validarEmail(): boolean {

return this.email.includes(&#039;@empresa.com&#039;);

}

}

const admin = new UsuarioAdmin(1, &#039;João&#039;, &#039;joao@empresa.com&#039;);

console.log(Admin validado: ${admin.validarEmail()}); // true</code></pre>

<p>Propriedades abstratas são úteis quando você quer garantir que cada subclasse tenha certas informações obrigatórias, não apenas comportamentos.</p>

<h2>Conclusão</h2>

<p>Aprendemos que <strong>classes abstratas definem contratos</strong> que suas subclasses devem seguir, evitando inconsistências em projetos grandes. Métodos abstratos garantem que comportamentos críticos sejam implementados corretamente em cada variação da classe. A chave é usar abstratas quando você tem múltiplas classes relacionadas que compartilham uma interface comum, mas diferem na implementação — como no exemplo do sistema de pagamentos. Este é um padrão fundamental da programação orientada a objetos e dominar isso coloca você no caminho para escrever código profissional e escalável.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://www.typescriptlang.org/docs/handbook/2/classes.html#abstract-classes-and-members" target="_blank" rel="noopener noreferrer">TypeScript Handbook - Abstract Classes</a></li>

<li><a href="https://developer.mozilla.org/pt-BR/docs/Web/JavaScript/Reference/Classes" target="_blank" rel="noopener noreferrer">MDN Web Docs - Herança em JavaScript/TypeScript</a></li>

<li><a href="https://www.oreilly.com/library/view/clean-code-a/9780136083238/" target="_blank" rel="noopener noreferrer">Clean Code by Robert C. Martin - Design Patterns Chapter</a></li>

<li><a href="https://basarat.gitbook.io/typescript/type-system/classes" target="_blank" rel="noopener noreferrer">TypeScript Deep Dive - Abstract Classes</a></li>

<li><a href="https://refactoring.guru/design-patterns" target="_blank" rel="noopener noreferrer">Refactoring.Guru - Design Patterns</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em TypeScript

Funções em TypeScript: Assinaturas, Overloads e this Tipado na Prática
Funções em TypeScript: Assinaturas, Overloads e this Tipado na Prática

Fundamentos de Funções em TypeScript Uma função em TypeScript é bem mais que...

Como Usar Metadata e Reflect-Metadata em TypeScript com Decorators em Produção
Como Usar Metadata e Reflect-Metadata em TypeScript com Decorators em Produção

Entendendo Metadata e Reflect-Metadata Metadata é simplesmente informação sob...

Dominando Project References em TypeScript: Monorepos e Builds Incrementais em Projetos Reais
Dominando Project References em TypeScript: Monorepos e Builds Incrementais em Projetos Reais

Project References: A Estrutura Fundamental do TypeScript Project References...