DEV Community

Yuri Peixinho
Yuri Peixinho

Posted on

POO: Herança

Introdução

Uma classe (subclasse/derivada) reaproveita estrutura e comportamento de outra (superclasse/base), podendo especializar o que for necessário.

class Funcionario
{
    public string Nome { get; set; }
    protected decimal SalarioBase;

    public Funcionario(string nome, decimal salarioBase)
    {
        Nome = nome;
        SalarioBase = salarioBase;
    }

    public virtual decimal CalcularSalario() => SalarioBase;
}

class Gerente : Funcionario
{
    private decimal _bonus;

    public Gerente(string nome, decimal salarioBase, decimal bonus) 
        : base(nome, salarioBase)
    {
        _bonus = bonus;
    }

    public override decimal CalcularSalario() => base.CalcularSalario() + _bonus;
}
Enter fullscreen mode Exit fullscreen mode

Repare em três mecanismos importantes:

  • : base(nome, salarioBase) — chama o construtor da classe pai.
  • virtual na base — sinaliza "isso pode ser reescrito".
  • override + base.CalcularSalario() — reescreve mas ainda reaproveita a lógica original.

Tipos de relação que herança modela

Regra de ouro é o teste "é um" (is-a): Gerente é um Funcionario. Se a relação real é "tem um" (has-a), a resposta certa é composição, não herança — Carro tem um Motor, não deveria herdar de Motor. O livro “Head First Design Pattern”, aborda de forma eficiente sobre preferir composições ao invés de herança.

sealed trava a herança:

sealed class RelatorioFinal : Funcionario /* ninguém pode herdar disso */
Enter fullscreen mode Exit fullscreen mode

Útil quando você quer garantir que o comportamento não será alterado por subclasses além de ser bom pra segurança e pra o compilador otimizar chamadas (sem precisar checar override em runtime).

A armadilha da herança profunda: cada nível adiciona acoplamento. Se Funcionario → Gerente → GerenteRegional → DiretorRegional, uma mudança lá em cima em Funcionario pode quebrar comportamento três níveis abaixo de um jeito difícil de rastrear. Regra prática: se você passar de 2 níveis de herança, pare e pergunte se composição resolveria melhor (voltamos nisso na conversa anterior, com o exemplo do IComportamentoVoo).

C# não tem herança múltipla de classes (só de interfaces) — justamente pra evitar o "problema do diamante": se C herdasse de A e B, e ambos tivessem um método Fazer(), qual implementação C usaria? C# resolve isso simplesmente proibindo.

Exemplo de jogo

Temos uma classe Personagem e agora criamos tipos especializados que reaproveitam tudo que Personagem já tem:

class Personagem
{
    // ...
    public void Atacar(Personagem alvo)
    {
        int dano = CalcularDano();          // fórmula escondida
        alvo.TomarDano(dano);
        Console.WriteLine($"{Nome} atacou {alvo.Nome} causando {dano} de dano!");
    }

    private int CalcularDano()
    {
        bool critico = new Random().Next(100) < 15; // 15% de chance
        int danoBase = 20;
        return critico ? danoBase * 2 : danoBase;
    }
}
Enter fullscreen mode Exit fullscreen mode
class Guerreiro : Personagem
{
    public int Armadura { get; set; }
    public Guerreiro(string nome) : base(nome, vidaMaxima: 150) { Armadura = 20; }
}

class Mago : Personagem
{
    public int Mana { get; private set; } = 100;
    public Mago(string nome) : base(nome, vidaMaxima: 80) { }
}

class Arqueiro : Personagem
{
    public Arqueiro(string nome) : base(nome, vidaMaxima: 100) { }
}
Enter fullscreen mode Exit fullscreen mode

Guerreiro, Mago e Arqueiro ganham Nome, Vida, Atacar(), TomarDano() e Curar() de graça — não precisaram reescrever nada disso. Cada um só adiciona o que é específico dele (Armadura, Mana).

Top comments (0)