O que é herança e quando faz sentido usar
Herança é um mecanismo de reutilização e especialização: você cria uma subclasse a partir de uma superclasse para reaproveitar comportamento e, quando necessário, especializar (alterar ou estender) partes desse comportamento.
Use herança quando a relação for do tipo “é um” (is-a). Ex.: um Desenvolvedor é um Funcionario. Se a relação for mais “tem um” (has-a), normalmente composição é mais adequada (ex.: um Funcionario tem um Endereco).
Boas práticas rápidas
- Prefira hierarquias rasas: 1 ou 2 níveis costumam ser suficientes.
- Evite “classes Deus” (superclasses gigantes) e subclasses que só existem para mudar detalhes mínimos.
- Garanta que a subclasse respeite o contrato da superclasse (mesma intenção, entradas/saídas compatíveis, invariantes preservadas).
Criando subclasses e reutilizando comportamento
Em Python, você cria uma subclasse colocando a superclasse entre parênteses:
class Funcionario: ...
class Desenvolvedor(Funcionario): ...A subclasse herda métodos e atributos públicos da superclasse. Você pode:
- Usar como está (reutilização direta).
- Sobrescrever (override) um método para mudar o comportamento.
- Estender um método, chamando a versão da superclasse e adicionando algo.
super(): chamando o construtor e métodos da superclasse
super() é a forma recomendada de acessar a implementação da superclasse, especialmente para:
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
- Chamar o construtor da superclasse dentro do
__init__da subclasse. - Estender métodos sobrescritos sem duplicar lógica.
Regra prática: se a superclasse faz validações e inicializações importantes, a subclasse deve chamar super().__init__(...) para manter o objeto consistente.
Mini-projeto progressivo: Funcionario → Desenvolvedor / Gerente
Neste mini-projeto, vamos modelar uma base Funcionario e duas especializações: Desenvolvedor e Gerente. O foco será: criação de subclasses, uso de super(), sobrescrita e extensão de comportamento, além de validações para manter o contrato.
Passo 1 — Superclasse Funcionario com contrato claro
Definiremos um contrato simples:
- Todo funcionário tem
nomenão vazio. - Todo funcionário tem
salario_basemaior que zero. - Todo funcionário sabe calcular seu
pagamento_mensal().
class Funcionario:
def __init__(self, nome: str, salario_base: float):
nome = (nome or "").strip()
if not nome:
raise ValueError("nome deve ser preenchido")
if salario_base <= 0:
raise ValueError("salario_base deve ser > 0")
self.nome = nome
self.salario_base = float(salario_base)
def pagamento_mensal(self) -> float:
"""Contrato: retorna um valor numérico > 0."""
return self.salario_base
def resumo(self) -> str:
return f"{self.nome} - pagamento: R$ {self.pagamento_mensal():.2f}"Note que pagamento_mensal() define um contrato: deve retornar um número positivo. As subclasses podem mudar como o pagamento é calculado, mas devem respeitar esse contrato.
Passo 2 — Subclasse Desenvolvedor: especialização com validação extra
Um desenvolvedor tem um nível (ex.: junior, pleno, senior) que influencia um bônus percentual. Aqui veremos:
- Uso de
super().__init__para reaproveitar validações e inicialização. - Sobrescrita de método para especializar o cálculo.
class Desenvolvedor(Funcionario):
BONUS_POR_NIVEL = {
"junior": 0.05,
"pleno": 0.10,
"senior": 0.20,
}
def __init__(self, nome: str, salario_base: float, nivel: str):
super().__init__(nome, salario_base)
nivel = (nivel or "").strip().lower()
if nivel not in self.BONUS_POR_NIVEL:
raise ValueError(f"nivel inválido: {nivel}")
self.nivel = nivel
def pagamento_mensal(self) -> float:
bonus = self.BONUS_POR_NIVEL[self.nivel]
return self.salario_base * (1 + bonus)Repare que a subclasse não reimplementa validação de nome e salario_base: ela delega isso à superclasse com super(). Isso reduz duplicação e mantém o contrato consistente.
Passo 3 — Subclasse Gerente: extensão de comportamento e invariantes
Um gerente terá um bônus fixo mensal e um limite de equipe. Aqui veremos:
- Validações específicas da subclasse.
- Extensão do comportamento: usar a base e somar algo.
class Gerente(Funcionario):
def __init__(self, nome: str, salario_base: float, bonus_mensal: float, tamanho_equipe: int = 0):
super().__init__(nome, salario_base)
if bonus_mensal < 0:
raise ValueError("bonus_mensal deve ser >= 0")
if tamanho_equipe < 0:
raise ValueError("tamanho_equipe deve ser >= 0")
self.bonus_mensal = float(bonus_mensal)
self.tamanho_equipe = int(tamanho_equipe)
def pagamento_mensal(self) -> float:
# Extensão: mantém a base e adiciona o bônus
return super().pagamento_mensal() + self.bonus_mensalObserve a diferença entre sobrescrever “do zero” e estender: aqui, super().pagamento_mensal() deixa explícito que o gerente parte do pagamento base do funcionário e adiciona um bônus.
Passo 4 — Polimorfismo na prática: tratar todos como Funcionario
Uma vantagem direta da herança é poder trabalhar com uma lista de Funcionario sem se preocupar com o tipo concreto. Cada objeto calcula seu pagamento conforme sua especialização.
equipe = [
Funcionario("Ana", 3000),
Desenvolvedor("Bruno", 5000, "pleno"),
Gerente("Carla", 8000, bonus_mensal=2000, tamanho_equipe=6),
]
folha = sum(f.pagamento_mensal() for f in equipe)
for f in equipe:
print(f.resumo())
print(f"Folha total: R$ {folha:.2f}")Sobrescrita com segurança: respeitando o contrato da superclasse
Quando você sobrescreve um método, a subclasse deve continuar “fazendo sentido” onde a superclasse era esperada. Na prática, isso significa:
- Não enfraquecer pré-condições de forma incompatível (ex.: exigir parâmetros mais restritos sem necessidade).
- Não quebrar pós-condições (ex.: o método prometia retornar um número positivo e a subclasse retorna
0ou negativo). - Manter invariantes (ex.: se
salario_basedeve ser > 0, a subclasse não deve permitir que vire 0/negativo por algum método).
Exemplo de sobrescrita problemática (o que evitar)
Imagine que alguém implemente um estagiário com pagamento zerado “para teste”. Isso quebra o contrato de pagamento_mensal() (retorno > 0) e pode causar bugs em cálculos de folha.
class Estagiario(Funcionario):
def pagamento_mensal(self) -> float:
return 0 # quebra o contratoSe estagiário não recebe salário, talvez ele nem devesse ser um Funcionario nesse modelo; ou então o contrato da superclasse deveria ser redefinido para comportar esse caso (o que afetaria todo o sistema).
Evite hierarquias profundas: sinais de alerta e alternativas
Hierarquias profundas tendem a ficar difíceis de entender e manter, porque:
- O comportamento real de um método pode depender de vários níveis de sobrescrita.
- Pequenas mudanças na superclasse podem quebrar subclasses distantes.
- Fica mais difícil testar combinações de regras.
Sinais de que a herança está passando do ponto
- Você tem classes como
Funcionario→FuncionarioCLT→FuncionarioCLTPleno→FuncionarioCLTPlenoRemoto… - Subclasses existem apenas para mudar um número/flag.
- Muitos
if isinstance(...)aparecem para “consertar” diferenças entre subclasses.
Como manter a especialização segura sem aprofundar demais
- Use herança para o núcleo comum e mantenha as variações em poucos níveis.
- Quando a variação for “plugável” (ex.: regras de bônus), considere extrair para um componente separado (composição) em vez de criar novas subclasses para cada regra.
- Prefira métodos pequenos e bem nomeados para facilitar extensão via
super().
Checklist prático ao criar uma subclasse
| Pergunta | Objetivo |
|---|---|
| Isso é realmente um “é um”? | Evitar herança onde composição seria melhor |
Preciso chamar super().__init__? | Garantir validações e inicialização do estado base |
| Vou sobrescrever ou estender? | Escolher entre substituir lógica ou reaproveitar e adicionar |
| Estou respeitando o contrato? | Manter entradas/saídas e invariantes compatíveis |
| Minha hierarquia está ficando profunda? | Evitar complexidade e acoplamento excessivos |