Módulo 1 · C# — Capítulo 06

OOP II — Herança e Interfaces

Como uma classe reaproveita e especializa comportamento de outra, e como código genérico consegue tratar tipos diferentes de forma uniforme — herança, interfaces e polimorfismo.

1. Herança — especializando uma classe

Herança permite que uma classe (derivada) reaproveite membros de outra (base), adicionando ou substituindo comportamento:

C# herança básica
public class Funcionario
{
    public string Nome { get; set; } = string.Empty;
    protected decimal SalarioBase { get; set; }

    public virtual decimal CalcularSalario() => SalarioBase;
}

public class Gerente : Funcionario
{
    public decimal Bonus { get; set; }

    public override decimal CalcularSalario() => base.CalcularSalario() + Bonus;
}

var gerente = new Gerente { Nome = "Ana", SalarioBase = 5000, Bonus = 1000 };
// Erro: SalarioBase é 'protected' e Nome/Bonus foram atribuídos via inicializador de objeto
// (válido pois são 'public' e Gerente herda de Funcionario)
Console.WriteLine(gerente.CalcularSalario()); // 6000
Palavra-chavePapel
: FuncionarioDeclara que Gerente herda de Funcionario
virtualMarca um método da classe base como "pode ser substituído"
overrideSubstitui a implementação de um método virtual da base
base.Chama a implementação original (da classe base) de dentro da versão sobrescrita
protectedVisível na própria classe e em qualquer classe que herde dela
Atenção

Sem virtual na classe base, override na derivada não compila. Esquecer virtual é o motivo mais comum de "meu método sobrescrito não está sendo chamado" para quem começa em C#.

2. Classes abstratas

Uma classe abstract não pode ser instanciada diretamente — ela existe só para ser herdada, geralmente definindo uma estrutura que as subclasses são obrigadas a completar:

C# classe abstrata
public abstract class FormaGeometrica
{
    public abstract double CalcularArea(); // sem corpo — obrigatório na subclasse

    public void Descrever() // método concreto, herdado como está
        => Console.WriteLine($"Área: {CalcularArea():F2}");
}

public class Retangulo : FormaGeometrica
{
    public double Largura { get; set; }
    public double Altura  { get; set; }

    public override double CalcularArea() => Largura * Altura;
}

// var forma = new FormaGeometrica(); -- não compila
var retangulo = new Retangulo { Largura = 4, Altura = 5 };
retangulo.Descrever(); // "Área: 20.00"

3. Interfaces — contrato sem implementação

Uma interface declara o quê uma classe deve fazer, sem dizer como. Diferente de herança (uma classe só pode ter uma classe base), uma classe pode implementar várias interfaces:

C# interfaces
public interface INotificavel
{
    void Notificar(string mensagem);
}

public interface IAuditavel
{
    DateTime CriadoEm { get; }
}

public class Cliente : INotificavel, IAuditavel
{
    public DateTime CriadoEm { get; } = DateTime.Now;

    public void Notificar(string mensagem)
        => Console.WriteLine($"Enviando: {mensagem}");
}
Classe abstrataInterface
Uma classe pode ter quantas?Só uma (herança única)Quantas quiser
Pode ter implementação padrão?Sim, métodos concretos + abstratosSim, desde C# 8 (default interface methods) — mas raro na prática
Pode ter campos/estado?SimNão
Uso típico"É um" — relação de especialização (Gerente é um Funcionario)"Pode fazer" — capacidade (Cliente pode ser notificado)

4. Polimorfismo — tratando tipos diferentes de forma uniforme

Este é o benefício prático de tudo o que veio antes: uma variável (ou parâmetro, ou item de lista) do tipo base pode guardar um objeto de qualquer classe derivada, e o método correto é chamado automaticamente:

C# polimorfismo em ação
List<FormaGeometrica> formas = new()
{
    new Retangulo { Largura = 4, Altura = 5 },
    new Circulo { Raio = 3 },
    new Triangulo { Base = 6, Altura = 4 },
};

foreach (var forma in formas)
    forma.Descrever(); // chama a versão correta de CalcularArea() para cada tipo
FormaGeometrica Retangulo Circulo Triangulo Uma List<FormaGeometrica> guarda qualquer um dos três — forma.CalcularArea() chama a versão certa em cada caso.

Fig. 1 — Polimorfismo: código escrito contra a classe base funciona com qualquer subclasse.

Comparando com TypeScript

Se você já usou TypeScript no Node, isso deve parecer familiar — interface existe com o mesmo espírito lá. A diferença é que em TypeScript a checagem de tipos desaparece na compilação para JS puro (é só uma ferramenta de desenvolvimento); em C#, o sistema de tipos é real e existe também em tempo de execução — dá para checar o tipo real de um objeto com is ou GetType() mesmo depois de compilado.

5. sealed — impedindo herança futura

C# sealed
public sealed class RelatorioFinal
{
    // ninguém pode herdar desta classe
}

// public class RelatorioEspecial : RelatorioFinal { } -- não compila

Use sealed quando uma classe representa um conceito final e completo — evita que futuras "especializações" acidentais quebrem premissas que o design original assumia.

6. Onde isso te leva

Com herança, interfaces e polimorfismo cobertos, você tem a base de orientação a objetos completa. O próximo capítulo aplica tudo isso a coleções — List<T>, Dictionary<TKey, TValue> e generics, que você já usou de forma intuitiva nos exemplos até aqui.

📌 Resumo do capítulo

  • virtual na classe base + override na derivada permite especializar comportamento; base. chama a versão original.
  • Classe abstract não pode ser instanciada e pode obrigar subclasses a implementar métodos específicos.
  • Interface define "pode fazer" (contrato, sem estado); uma classe implementa várias; herança de classe é única.
  • Polimorfismo: uma coleção/variável do tipo base aceita qualquer subtipo, chamando a implementação correta automaticamente.
  • sealed impede que uma classe seja herdada.

✏️ Praticando

  1. Implemente FormaGeometrica, Retangulo, Circulo e Triangulo como nos exemplos, e uma List<FormaGeometrica> com uma de cada.
  2. Crie a interface IPersistivel com um método Task SalvarAsync(), e implemente-a em Cliente e Produto ao mesmo tempo que herdam de alguma classe base (se fizer sentido no seu modelo).
  3. Explique, em suas próprias palavras, quando você usaria uma classe abstrata em vez de uma interface para o mesmo problema.