Herança

Durante a modelagem de sistemas computacionais, observaremos várias situações em que diferentes classes compartilham mesmo atributos e métodos. Imagine, por exemplo, um sistema que envolve o cadastro de cursos de uma instituição de ensino. Um classe que representa um curso de graduação terá muitos membros (atributos e métodos) em comum com a classe que representa o curso de pós-graduação.

Há, no entanto, algumas particularidades. A classe CursoGraduacao, por exemplo, pode ter um indicador do conceito ENADE (Exame Nacional de Avaliação de Egressos) do curso. A classe CursoPosGraduacao, por sua vez, pode ter um atributo para identificar qual é a área associada junto à CAPES (Coordenação de Aperfeiçoamento de Pessoal de Ensino Superior).

Além dos atributos e métodos, um fator importante a se observar quando se fala de herança é as associações que cada classe terá. Uma tese de doutorado, por exemplo, deve ser vinculada a um curso de pós-graduação. Qualquer vinculação desta classe com curso de pós-graduação seria incorreta. Da mesma forma, uma disciplina de curso de graduação não pode ser vinculada a um curso de graduação.

Uma solução intermediária

Até aqui, já devemos concordar que a existência de três classes em nosso programa.

export class Curso{
    private _nome: string;

    constructor(nome: string){
        this._nome = nome;
    }
    get nome(){
        return this._nome;
    }
    set nome(nome: string){
        this._nome = nome;
    }
}
export class CursoGraduacao{
    private _curso: Curso;
    private _conceitoENADE: number;

    constructor(curso: Curso, conceitoENADE: number){
        this._curso = curso;
        this._conceitoENADE = enade;
    }
    get conceitoENADE(){
        return this._conceitoENADE;
    }
    set conceitoENADE(conceitoENADE: number){
        this._conceitoENADE = conceitoENADE;
    }
}

Para acessar o nome do curso, teríamos a seguinte operação:

let engcomp = new CursoGraduacao(new Curso("Engenharia de Computação"), 5);
console.log(engcomp.curso.nome);

Observe que uma leitura desta implementação seria: "curso de graduação tem curso e tem ENADE". De fato, o curso de graduação tem conceito ENADE, mas não soa correto dizer que o curso de graduação tem um curso. Ele é um curso.

Aplicando a herança

Nesse tipo de situação, usamos o conceito de herança, que faz justamente essa mudança de "tem" para "é". Na implementação de Curso, por enquanto, nada muda. Diremos, nas classes filhas, que elas "herdam" as propriedades da classe Curso.

export class CursoGraduacao extends Curso{
    private _conceitoENADE: number;

    constructor(nome: string, conceitoENADE: number){
        super(nome);
        this._conceitoENADE = enade;
    }
    get conceitoENADE(){
        return this._conceitoENADE;
    }
    set conceitoENADE(conceitoENADE: number){
        this._conceitoENADE = conceitoENADE;
    }
}
export class CursoPosGraduacao extends Curso{
    private _areaCAPES: number;

    constructor(nome: string, areaCAPES: number){
        super(nome);
        this._areaCAPES = areaCAPES;
    }
    get areaCAPES(){
        return this_.areaCAPES;
    }
    set areaCAPES(areaCAPES: number){
        this._areaCAPES = areaCAPES;
    }
}

Agora, a criação de um objeto para o curso de Engenharia de Computação ficaria da seguinte forma:

let engcomp = new CursoGraduacao("Engenharia da Computação", 5);
console.log(engcomp.nome);

Limitando a herança

Em alguns linguagens de programação, como é o caso de Java, pode-se impedir que uma classe tenha herdeiros, usando a palavra reservada final. Esse recurso não está disponível em TypeScript.

Herança múltipla

Algumas linguagens de programação permitem que uma classe herde membros de mais uma classe. Isso não é possível em TypeScript ou em Java.