Fundamentos da programação 10 min de leitura 27 mar 2026

Herança: O segundo pilar da Programação Orientada a Objetos

Fala Dev, tudo em riba?

Vamos dar continuidade à nossa série de artigos sobre Programação Orientada a Objetos (POO), porém antes vamos relembrar o que abordamos até o momento.

Entendemos o que é, para que serve, e quais os ganhos que temos ao utilizar o paradigma de POO. Abordamos também o primeiro pilar dos quatro principais da POO, o Encapsulamento que, de forma simplista, é proteger o estado interno do seu objeto.

Se você ainda não leu os artigos anteriores, recomendo que você pause a leitura desse artigo e leia os artigos anteriores, pois os conceitos neles abordados são extremamente necessários para um melhor entendimento do pilar que abordaremos nesse artigo.

Neste artigo abordaremos o segundo pilar da POO, a Herança. Então pegue sua xícara de café e vamos ao artigo!

Um pouco de contexto

Antes de irmos para a definição do que é Herança, primeiramente vamos entrar no contexto onde esse pilar pode (na verdade deve) ser aplicado.

Vamos pensar em uma aplicação backend no qual temos os clientes (Customer) que podem utilizar os recursos disponíveis nessa aplicação e os administradores (Admin) que têm acesso a áreas de gestão dos recursos disponibilizados nessa referida aplicação.

Em um primeiro momento podemos pensar “olha, para cuidar dos clientes terei uma classe Customer e para cuidar dos administradores, terei uma classe Admin”. Então teríamos o seguinte código na aplicação.

class Customer {
  constructor(
    public id: string,
    public name: string,
    public email: string,
    public shippingAddress: string
  ) {}

  getDisplayName(): string {
    return `${this.name} <${this.email}>`;
  }
}

class Admin {
  constructor(
    public id: string,
    public name: string,
    public email: string,
    public accessLevel: number
  ) {}

  getDisplayName(): string {
    return `${this.name} <${this.email}>`;
  }
}

Conseguiu identificar o problema aqui? O código em si está perfeito, mas mesmo ele estando perfeito ainda sim tem um problema, conseguiu identificar? Vamos a ele!

Repare que tanto a classe Customer quanto a classe Admin compartilham características (atributos) e comportamentos (métodos) iguais. Ambas as classes têm os atributos idname e email. Ambas tem também um mesmo método getDisplayName que faz a mesma coisa, tem o mesmo comportamento. A única diferença entre as classes é que a classe Customer tem um atributo shippingAddress e a classe Admin tem um atributo accessLevel. De resto, são idênticas.

Isso parece algo pequeno se sua aplicação for pequena, mas o ideal é que toda aplicação seja concebida de forma que seu crescimento seja facilitado tanto no crescimento em si quanto na manutenibilidade da aplicação à medida que ela cresce.

Ter uma infinidade de objetos idênticos pode ser uma dor de cabeça no futuro, pois a complexidade de manutenção e o volume de código pode impactar diretamente na qualidade da aplicação como um todo e dificultar adição de novas features.

Tá, mas como melhorar isso? Com o pilar da POO que vamos conhecer agora.

O que é Herança no contexto da POO?

A Herança, como o nome mesmo pode sugerir, é a possibilidade de uma classe herdar características (atributos) e comportamentos (métodos) de uma outra classe e desse ponto em diante adicionar características e comportamentos adicionais de acordo com necessidade.

Desse modo evitamos repetição de código e criamos uma relação clara do tipo “é um”. Sendo assim, trazendo para o cenário apresentado anteriormente, podemos dizer:

Ambos compartilham o que todo User tem, mas cada um tem suas particularidades. E ai aparecem as nomenclaturas classe pai e classe filha. Chamamos de classe pai a classe que vai oferecer sua estrutura, seus atributos e métodos para outras classes. E classes filhas são as classes que recebem atributos e métodos de outras classes e acrescentam seus próprios atributos e métodos.

Herança na prática

Ficou complicado de entender? Vamos refatorar o código anterior aplicando Herança para clarear as coisas.

// Classe PAI (superclasse / classe base)
class User {
  constructor(
    public readonly id: string,
    public name: string,
    public email: string
  ) {}

  getDisplayName(): string {
    return `${this.name} <${this.email}>`;
  }
}

// Classe FILHA — herda tudo de User
class Customer extends User {
  constructor(
    id: string,
    name: string,
    email: string,
    public shippingAddress: string // atributo específico de Customer
  ) {
    super(id, name, email); // obrigatório: chama o construtor da classe pai
  }

  // Método específico de Customer
  getShippingInfo(): string {
    return `${this.name} — ${this.shippingAddress}`;
  }
}

class Admin extends User {
  constructor(
    id: string,
    name: string,
    email: string,
    public accessLevel: number // atributo específico de Admin
  ) {
    super(id, name, email);
  }

  hasFullAccess(): boolean {
    return this.accessLevel === 1;
  }
}

Analisando o código acima:

Olha que mudança! Reaproveitamos trechos de código que se repetiam e cada nova classe acrescentou suas características e comportamentos. E como utilizamos essas classes, como criamos os objetos? Segue o código:

const customer = new Customer("1", "Carlos", "carlos@email.com", "Rua A, 123");
const admin = new Admin("2", "Ana", "ana@email.com", 1);

// Métodos herdados de User
console.log(customer.getDisplayName()); // Carlos <carlos@email.com>
console.log(admin.getDisplayName());    // Ana <ana@email.com>

// Métodos específicos de cada classe
console.log(customer.getShippingInfo()); // Carlos — Rua A, 123
console.log(admin.hasFullAccess());      // true

Explicando:

Tá, e esse tal de super presente nos construtores?

Esse tal de super é a ponte entre a classe filha e a classe pai. Repara que no construtor da classe Customer, os atributos herdados da classe User são repassados através do super para a classe User. Isso ocorre pois a classe User tem um construtor que recebe esses atributos. Quando fazemos a herança, o construtor da classe User é executado e ele precisa dos valores de tais atributos. O mesmo ocorre na classe Admin.

super pode ser utilizado em dois momentos:

constructor(id: string, name: string, email: string, public accessLevel: number) {
  super(id, name, email); // primeiro chama o pai
  // só depois pode usar this.
}
class Admin extends User {
  getDisplayName(): string {
    // Reutiliza o método do pai e adiciona algo a mais
    return `[ADMIN] ${super.getDisplayName()}`;
  }
}

const admin = new Admin("1", "Ana", "ana@email.com", 1);
console.log(admin.getDisplayName()); // [ADMIN] Ana <ana@email.com>

O modificador protected

Finalmente vamos entender de forma completa quando utilizamos o modificador protected (digo finalmente pois venho falando desse carinha nos últimos dois artigos). Esse modificador faz total sentido quando utilizamos herança.

O modificador private restringe o acesso de um atributo ou método ao mundo externo, ou seja, somente a própria classe tem acesso a ele. Já o modificador protected restringe o acesso de um atributo ou método ao mundo externo, porém é acessível pelas classes filhas. Vamos a um exemplo:

class User {
  constructor(
    public readonly id: string,
    public name: string,
    protected email: string // acessível em filhas, mas não fora da hierarquia
  ) {}
}

class Admin extends User {
  getContact(): string {
    return this.email;
  }
}

const admin = new Admin("1", "Ana", "ana@email.com");
console.log(admin.email); // ❌ erro — email é protected, não public
console.log(admin.getContact()); // ✅ Admin pode acessar porque herda de User

No código acima, o atributo email recebeu o modificador protected na classe User. Ao instanciar um objeto do tipo Admin, o atributo email recebe um valor. Se tentar acessar esse valor através de admin.email, é retornado um erro, pois o atributo email não é acessível ao mundo externo. No entanto, é possível acessar o atributo via método getContact da classe Admin, pois a classe tem acesso a esse atributo.

Principais benefícios em se utilizar herança

Conclusão

A herança é um pilar com um papel muito interessante na POO, que contribui e muito para construção de uma aplicação organizada e robusta, preparada para receber novas features sem a necessidade de replicar ou repetir código e contribuindo com uma melhor manutenibilidade do code base.

Uma observação importante a ser feita é que, apesar de poderosa, a herança deve ser utilizada com critério, com moderação. Não devemos aplicar o conceito de herança para tudo. A regra de outro é a seguinte:

Espero que esse artigo tenha contribuído para seu aprendizado ou entendimento sobre o conceito de Herança, um dos 4 pilares da Programação Orientada a Objetos. No próximo artigo veremos o terceiro pilar, o Polimorfismo.

Forte abraço e até mais!

#backend #OOP #POO #ProgramaçàoOrientadaObjetos #typescript