Classes abstratas

Logo no início da disciplina, discutimos o conceito de abstração, que envolve basicamente a concepção de quais classes devem ser criadas para a representação de um determinado cenário (e, consequentemente, quais atributos e métodos farão parte de cada classe).

Agora, veremos um outro conceito, que usa um termo parecido, mas indica outro aspecto: classes abstratas. Indo direto ao ponto, uma classe abstrata é uma classe que não pode ser instanciada. Ou seja: essa classe não terá objetos. Veja o exemplo de código a seguir, em que definimos uma classe abstrata simplesmente adicionado o termo abstract antes da palavra class:

export abstract class Pessoa {
  private _nome: string;

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

  get nome(): string {
    return this._nome;
  }
  set nome(nome: string) {
    this._nome = nome;
  }
}

A execução da linha a seguir resultará em erro, pois Pessoa não pode ser instanciada:

let joao:Pessoa = new Pessoa("Joao");

Num primeiro momento, essa ideia pode ser bastante estranha: afinal, por que criaremos uma classe que não pode ser instanciada? Um exemplo interessante de situação em que a classe abstrata é importante é um caso em que usamos herança e queremos que apenas as subclasses sejam instanciadas. Acompanhe o exemplo a seguir:

export abstract class Pessoa{
    /* implementação */
}

export class PessoaProfessor extends Pessoa{
    /* implementação */
}

export class PessoaEstudante extends Pessoa{
    /* implementação */
}

No caso acima, o programa pode ter instâncias das classes Professor e Estudante, mas não de Pessoa. Isso garante que apenas objetos das subclasses sejam cadastrados no sistema.

Métodos abstratos

Um recurso bastante interessante das classes abstratas está na possibilidade de definição de métodos abstratos. Esses métodos não possuem implementação, apenas a assinatura, como apresentado no exemplo a seguir:

export abstract class Pessoa{
    /* ... */
    
    abstract imprimirFicha(): void;
}

Essa inclusão implicará na obrigatoriedade de implementação do método ****imprimirFicha pelas classes herdeiras de Pessoa . Essa estratégia é oportuna quando todas as subclasses devem oferecer o mesmo recurso, mas com implementação muito diferente para cada uma delas.

Métodos concretos

As classes abstratas podem, evidentemente, possuir métodos com implementação. Chamados de métodos concretos, são os métodos tradicionais, que serão herdados naturalmente pelas subclasses.

👨🏼‍💻 Veja o código completo

export abstract class Pessoa {
  private _nome: string;

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

  get nome(): string {
    return this._nome;
  }

  set nome(nome: string) {
    this._nome = nome;
  }

  abstract imprimirFicha(): void;

  saudar(): string {
    return `Olá, meu nome é ${this._nome}.`;
  }
}
import { Pessoa } from "./Pessoa";

export class PessoaProfessor extends Pessoa {
  private _siape: number;

  constructor(nome: string, siape: number) {
    super(nome);
    this._siape = siape;
  }

  get siape(): number {
    return this._siape;
  }

  set siape(siape: number) {
    this._siape = siape;
  }

  imprimirFicha(): void { // obrigatório implementar
    console.log(`Ficha do Professor: ${super.nome}`);
  }
}
import { Pessoa } from "./Pessoa";

export class PessoaEstudante extends Pessoa {
  private _rga: number;

  constructor(nome: string, rga: number) {
    super(nome);
    this._rga = rga;
  }

  get siape(): number {
    return this._rga;
  }

  set siape(rga: number) {
    this._rga = rga;
  }

  imprimirFicha(): void { // obrigatório implementar
    console.log(`Ficha do Estudante: ${super.nome}`);
  }
}

🚀 Para testar na prática