Guia de estudo

15/05/2026 · Aula 10

Classes Abstratas e Interfaces

Quando a sugestão vira lei.

Programação Orientada a Objetos com Python
Tecnologia em Análise e Desenvolvimento de Sistemas

Módulo abc @abstractmethod Contratos formais O 4º pilar: Abstração

plano de voo

O que vamos aprender hoje

01

O Buraco

Por que o polimorfismo da aula 09 ainda deixa um risco aberto.

02

A Lei

Como o módulo abc obriga subclasses a implementar métodos.

03

Abstração

Fechamos o quarto pilar da POO — agora você conhece os quatro.

04

Na Prática

Sistema de pagamentos com contrato formal entre as classes.

de onde paramos · 9 dias atrás

Polimorfismo — o que vimos na aula 09

  • 🎭 Polimorfismo é o mesmo método se comportando diferente em cada classe — calcular_salario() num gerente, num analista, num estagiário.
  • 🪓 O ganho: aquele if tipo == "gerente"... que cresce sem fim some — quem chama o método não precisa saber o tipo concreto.
  • 🛣️ Dois caminhos: via herança (subclasse sobrescreve método do pai) ou via duck typing (qualquer objeto com o método certo serve).

memória muscular

O exemplo central da aula 09

Uma só função processa qualquer funcionário — porque cada classe sabe calcular o próprio salário.

🎯
A função processar_folha não pergunta o tipo do funcionário — só confia que cada objeto sabe calcular o próprio salário.
🧬
Cada subclasse sobrescreve calcular_salario à sua maneira — polimorfismo em ação.
folha_aula_09.py
class Funcionario:
    def __init__(self, nome, base):
        self.nome = nome
        self.base = base
    def calcular_salario(self):
        return self.base

class Gerente(Funcionario):
    def calcular_salario(self):
        return self.base + 2000  # bônus de liderança

class Vendedor(Funcionario):
    def __init__(self, nome, base, comissao):
        super().__init__(nome, base)
        self.comissao = comissao
    def calcular_salario(self):
        return self.base + self.comissao

def processar_folha(funcionarios):
    for f in funcionarios:
        print(f"{f.nome}: R$ {f.calcular_salario():.2f}")

processar_folha([
    Funcionario("Ana", 3000),
    Gerente("Bruno", 5000),
    Vendedor("Carla", 2000, 1500),
])

pergunta pra turma · 2 minutos

Pra esquentar a cabeça

  • Se eu criar uma classe Estagiario(Funcionario) e esquecer de escrever o método calcular_salario, o que acontece quando processar_folha for chamada com um estagiário na lista?

a resposta · e por que ela importa

O silêncio do método herdado

  • 🤫 O Python procura calcular_salario em Estagiario, não acha, sobe a hierarquia e usa o de Funcionario — que devolve só self.base.
  • 🚩 Resultado: o estagiário recebe salário cheio de funcionário. Nenhuma exceção, nenhum log — só um número errado na folha de pagamento.

É exatamente o problema de hoje

Como fazer o Python se recusar a criar uma subclasse incompleta? É pra isso que serve o módulo abc.

voltando da aula anterior

Aula 09 nos deixou uma pendência

  • 🎭 O que funcionou: polimorfismo deixou o código limpo — qualquer forma respondia a area().
  • 🕳️ O que ficou aberto: nada impedia uma subclasse de esquecer de implementar e herdar o return 0 em silêncio.

A pergunta de hoje

Como transformar um método em obrigação, não em sugestão?

começando pelo problema

O bug que não dá erro

  • 😬 Cenário: a equipe adicionou Hexagono ao sistema, mas esqueceu de implementar area().
  • 💥 Resultado: o programa roda, calcula relatórios, gera faturas — tudo com áreas zeradas. Ninguém percebe.

Erro comum

Um return 0 ou pass na classe-mãe parece inofensivo, mas vira uma armadilha invisível.

o problema em código

Python não reclama. E esse é o problema.

Veja como uma subclasse incompleta passa despercebida. Não há exceção, não há aviso — só números errados.

🔴
O return 0 é uma sugestão que a subclasse pode (e vai) ignorar sem que ninguém perceba.
🐛
Esse é o tipo de bug que chega em produção — testes podem passar porque o método existe.
problema_sem_abc.py
class Forma:
    def area(self):
        return 0  # placeholder: "alguém implementa depois"

class Circulo(Forma):
    def __init__(self, raio):
        self.raio = raio
    def area(self):
        return 3.14 * self.raio ** 2

class Hexagono(Forma):
    def __init__(self, lado):
        self.lado = lado
    # esqueceu de implementar area()...

h = Hexagono(lado=10)
print(h.area())  # 0 — errado, mas sem erro

a ideia da solução

E se o Python recusasse criar o objeto?

  • 🚧 A ideia: marcar certos métodos como obrigatórios. Sem implementação, sem objeto.
  • O efeito: o erro aparece imediatamente, com mensagem clara — não em produção, três semanas depois.

Mudança de mentalidade

Saímos do "talvez funcione" para o "ou implementa, ou não roda".

as peças que você vai encontrar

Antes do código, conhece quem entra em cena

  • 🏛️ ABC é uma classe-mãe especial que vem com o Python, no módulo abc. Quando sua classe herda dela, é como pendurar uma plaquinha gigante dizendo "esta classe é abstrata". Sem isso, o Python não sabe que tem que reclamar.
  • 📜 @abstractmethod é um decorador — igualzinho ao @property da aula 04. Você cola em cima de um método e ele ganha um significado especial. O @abstractmethod diz "este método é obrigatório nas subclasses". Mesmo padrão, significado diferente.

Não tem mágica

Por trás disso, o Python tá só usando dois recursos da própria linguagem — uma classe e um decorador. Você vê exatamente o que ele vê.

a solução em código

O módulo abc + @abstractmethod

Duas linhas mudam tudo: from abc import ABC, abstractmethod e o decorador acima do método.

🧬
ABC é a classe-mãe de toda classe abstrata. Herdar dela ativa o sistema de contratos.
📜
@abstractmethod é a etiqueta: "este método é parte do contrato — implemente ou não instancie".
solucao_com_abc.py
from abc import ABC, abstractmethod

class Forma(ABC):                  # agora é uma classe abstrata
    @abstractmethod
    def area(self):                # contrato: subclasses DEVEM implementar
        pass

class Circulo(Forma):
    def __init__(self, raio):
        self.raio = raio
    def area(self):                # contrato cumprido
        return 3.14 * self.raio ** 2

class Hexagono(Forma):
    def __init__(self, lado):
        self.lado = lado
    # esqueceu de implementar area()...

o que muda do lado de fora

Agora o erro aparece na hora certa

Tente criar um Hexagono sem implementar area() — o Python recusa antes mesmo do objeto nascer.

🛑
Hexagono não nasce — o Python recusa porque o contrato não foi cumprido.
🚫
Nem a própria Forma pode ser instanciada: ela é abstrata, existe só para ser herdada.
uso_com_abc.py
>>> from solucao_com_abc import Circulo, Hexagono, Forma
>>> c = Circulo(raio=5)
>>> print(c.area())
78.5

>>> h = Hexagono(lado=10)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    h = Hexagono(lado=10)
        ^^^^^^^^^^^^^^^^^
TypeError: Can't instantiate abstract class Hexagono without an implementation for abstract method 'area'

>>> f = Forma()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    f = Forma()
        ^^^^^^^
TypeError: Can't instantiate abstract class Forma without an implementation for abstract method 'area'

agora podemos dar o nome

Isso se chama Classe Abstrata

  • 🏛️ Classe abstrata (ABC): serve como modelo — define o que toda subclasse precisa ter, sem dizer como.
  • 📐 Método abstrato: método declarado com @abstractmethod. O corpo é só pass — quem dá o corpo são as subclasses.

Regra de ouro

Classe abstrata = molde. Tentar instanciar um molde não faz sentido — o que vale é o que sai dele.

antes e depois · lado a lado

A diferença que o ABC faz

❌ Sem ABC (sugestão)

  • Subclasse pode esquecer o método
  • Erro silencioso: return 0 herdado
  • Bug aparece em produção
  • Contrato existe só no comentário

✅ Com ABC (lei)

  • Subclasse é obrigada a implementar
  • Erro alto e claro: TypeError
  • Bug aparece no primeiro teste
  • Contrato está no próprio código

analogia do cotidiano

Lei vs. sugestão

  • 🪧 Sugestão (pass / return 0): a placa pede educadamente. Quem quiser ignora — e ninguém checa.
  • 🚪 Lei (@abstractmethod): a porta literalmente não abre. Você implementa ou não passa.

Outra forma de pensar

A classe abstrata é a planta baixa de um prédio. Você não mora na planta — mora no prédio construído a partir dela.

anatomia

As quatro partes de uma classe abstrata

Toda classe abstrata em Python tem o mesmo esqueleto. Memorize esses quatro pontos.

1

Importar

from abc import ABC, abstractmethod — sempre os dois juntos.

2

Herdar de ABC

class Forma(ABC): — esse é o gatilho do sistema de contratos.

3

Decorar com @abstractmethod

Marque assim os métodos que toda subclasse deve implementar.

4

Corpo é pass

O método abstrato não precisa de corpo — quem dá a implementação é a subclasse.

abstrato e concreto convivem

Nem todo método precisa ser abstrato

Uma classe abstrata pode ter métodos abstratos e métodos concretos. Os concretos são herdados normalmente — sem precisar reimplementar.

🧩
Métodos concretos (como descrever) são herdados sem esforço — código reutilizado.
🎯
Métodos abstratos (como mover) marcam apenas o que varia entre subclasses.
abstrato_e_concreto.py
from abc import ABC, abstractmethod

class Veiculo(ABC):
    def __init__(self, marca):
        self.marca = marca

    @abstractmethod
    def mover(self):              # abstrato: subclasses DEVEM implementar
        pass

    def descrever(self):          # concreto: herdado tal qual
        print(f"Veículo da marca {self.marca}")

class Carro(Veiculo):
    def mover(self):              # só este é obrigatório
        print(f"Carro {self.marca} andando na estrada")

c = Carro("Toyota")
c.mover()        # Carro Toyota andando na estrada
c.descrever()    # Veículo da marca Toyota  ← herdado, sem reescrever

fechamento dos pilares

O quarto pilar da POO: Abstração

  • 🔒 Encapsulamento (aula 04) protege o estado. Herança (aulas 07-08) reaproveita código.
  • 🎭 Polimorfismo (aula 09) faz a mesma chamada agir diferente. Abstração (hoje) define o contrato que torna tudo isso confiável.

Para guardar

Encapsulamento esconde dados. Abstração esconde complexidade e define contratos.

interface = várias abstrações juntas

Quando há mais de um método abstrato

Em outras linguagens (Java, C#) chamam isso de interface: um contrato com vários métodos obrigatórios. Em Python, é só uma classe abstrata com vários @abstractmethod.

📑
Quem herda de Reprodutor é obrigado a implementar os dois métodos — não basta um.
🐍
Em Python, não existe interface como palavra-chave — usamos ABC com vários @abstractmethod.
🪞
Convenção: quando a abstrata tem métodos abstratos — sem atributos, sem __init__, sem métodos concretos — é o que outras linguagens chamam de interface pura. Quando tem estado (como a Veiculo do slide anterior), é uma classe abstrata "completa".
interface_reprodutor.py
from abc import ABC, abstractmethod

class Reprodutor(ABC):
    @abstractmethod
    def play(self):
        pass

    @abstractmethod
    def pause(self):
        pass

class Spotify(Reprodutor):
    def play(self):
        print("Tocando música no Spotify")

    def pause(self):
        print("Pausando música")

class YouTube(Reprodutor):
    def play(self):
        print("Tocando vídeo no YouTube")

    def pause(self):
        print("Pausando vídeo")

s = Spotify()
s.play()    # Tocando música no Spotify
s.pause()   # Pausando música

por que isso importa

O que classes abstratas entregam

🛡️

Segurança

Bugs de implementação esquecida não chegam em produção.

📐

Clareza

O contrato está no código, não em comentários ou documentação solta.

🤝

Trabalho em equipe

Quem implementa uma subclasse sabe exatamente o que precisa entregar.

🧪

Testabilidade

Você pode escrever testes contra o contrato, não contra cada classe.

🧹

Código limpo

Reaproveita métodos concretos e força implementação só do que varia.

🚀

Extensibilidade

Novas subclasses encaixam sem alterar quem já usa a abstrata.

uso com critério

Quando usar (e quando não usar)

  • Use quando: você tem 2+ subclasses concretas que precisam compartilhar a mesma assinatura de método.
  • Evite quando: você só tem uma subclasse, ou quando o comportamento ainda está sendo descoberto — abstraia depois.

Princípio prático

Comece concreto. Quando aparecer a segunda classe parecida, aí promove para abstrata.

glossário rápido

Os termos que você precisa lembrar

item detalhe
ABC Abstract Base Class. Classe-base de toda classe abstrata em Python. Importada de abc.
@abstractmethod Decorador que marca um método como obrigatório nas subclasses.
Classe abstrata Classe que herda de ABC e tem ao menos um método abstrato. Não pode ser instanciada.
Método concreto Método com corpo de verdade. Pode coexistir com métodos abstratos na mesma classe.
Interface Termo de outras linguagens. Em Python: uma classe abstrata só com @abstractmethod, sem estado.
Contrato Conjunto de métodos que toda subclasse promete implementar. ABC torna o contrato obrigatório.

laboratório em sala · duplas

Exercício ao Vivo: Sistema de Pagamentos

Em duplas. 25 minutos. Objetivo: usar ABC para formalizar o contrato do polimorfismo de pagamentos da aula 09.

1

Crie a abstrata

Classe Pagamento(ABC) com dois métodos abstratos: processar(valor) e gerar_recibo(valor).

2

Crie 3 subclasses

PagamentoCartao, PagamentoPix e PagamentoBoleto — cada uma implementa os dois métodos.

3

Função de processamento

Função processar_pagamentos(lista, valor) que percorre uma lista heterogênea de pagamentos.

4

Teste o contrato

Crie uma classe PagamentoFalho(Pagamento) que esquece gerar_recibo e veja o TypeError em ação.

resolução · passo 1

Começamos pela abstrata

  • 📦 Importamos ABC e abstractmethod do módulo abc.
  • 📜 Declaramos dois métodos abstratos: processar (faz o débito) e gerar_recibo (devolve a string do comprovante).

Detalhe importante

A abstrata pode ter um __init__ normal — só os métodos marcados são obrigatórios.

resolução · passo 1

A classe abstrata Pagamento

Cinco linhas concentram todo o contrato do sistema.

📌
O __init__ é concreto — toda subclasse já recebe cliente e data de graça.
🔐
Os dois @abstractmethod formam o contrato: nenhuma subclasse passa sem implementar ambos.
pagamentos.py
from abc import ABC, abstractmethod
from datetime import datetime

class Pagamento(ABC):
    def __init__(self, cliente):
        self.cliente = cliente
        self.data = datetime.now()

    @abstractmethod
    def processar(self, valor):
        pass

    @abstractmethod
    def gerar_recibo(self, valor):
        pass

resolução · passo 2

As três subclasses concretas

Cada forma de pagamento implementa o contrato do seu jeito — e o construtor da mãe é reaproveitado com super().

🧬
PagamentoCartao precisa de um atributo extra (numero), então declara seu __init__ e chama super().__init__(cliente). Pix e Boleto não precisam de nada além — herdam o __init__ da mãe sem declarar.
🎭
Cada subclasse cumpre o mesmo contrato com comportamento próprio — polimorfismo da aula 09 em ação, agora formalizado.
pagamentos.py (continuação)
class PagamentoCartao(Pagamento):
    def __init__(self, cliente, numero_cartao):
        super().__init__(cliente)
        self.numero = numero_cartao

    def processar(self, valor):
        print(f"Cobrando R$ {valor:.2f} no cartão ****{self.numero[-4:]}")
        return True

    def gerar_recibo(self, valor):
        return f"[CARTÃO] {self.cliente} — R$ {valor:.2f}"

class PagamentoPix(Pagamento):
    # __init__ herdado de Pagamento (recebe cliente, define data)
    def processar(self, valor):
        print(f"Transferência Pix de R$ {valor:.2f} de {self.cliente}")
        return True

    def gerar_recibo(self, valor):
        return f"[PIX] {self.cliente} — R$ {valor:.2f}"

class PagamentoBoleto(Pagamento):
    # __init__ herdado de Pagamento (recebe cliente, define data)
    def processar(self, valor):
        print(f"Boleto gerado no valor de R$ {valor:.2f}")
        return False  # aguardando confirmação do banco

    def gerar_recibo(self, valor):
        return f"[BOLETO] {self.cliente} — R$ {valor:.2f} (pendente)"

resolução · passos 3 e 4

A função poliforma + o teste do contrato

Uma só função processa qualquer pagamento. E uma subclasse incompleta nem chega a nascer.

🔁
A função processar_pagamentos não sabe (nem precisa saber) o tipo concreto — só confia no contrato.
🚨
PagamentoFalho é recusada na hora — o erro vem antes do objeto existir.
main.py
def processar_pagamentos(lista, valor):
    for p in lista:
        ok = p.processar(valor)
        status = "OK" if ok else "PENDENTE"
        print(f"  -> {status} | {p.gerar_recibo(valor)}")

pagamentos = [
    PagamentoCartao("Ana",   "1234567890124444"),
    PagamentoPix("Bruno"),
    PagamentoBoleto("Carla"),
]
processar_pagamentos(pagamentos, valor=150.00)

# passo 4: subclasse incompleta
class PagamentoFalho(Pagamento):
    def processar(self, valor):
        return True
    # esqueceu gerar_recibo()...

PagamentoFalho("Diego")  # 💥 TypeError nesta linha

resolução · saída

Saída esperada no terminal

Os três primeiros pagamentos rodam limpos. O quarto trava com a mensagem certa, na hora certa.

Os pagamentos válidos rodam normalmente — o contrato bem cumprido libera o polimorfismo.
🛑
A mensagem do TypeError diz exatamente qual classe e qual método falhou — debug em segundos.
terminal
$ python main.py
Cobrando R$ 150.00 no cartão ****4444
  -> OK | [CARTÃO] Ana — R$ 150.00
Transferência Pix de R$ 150.00 de Bruno
  -> OK | [PIX] Bruno — R$ 150.00
Boleto gerado no valor de R$ 150.00
  -> PENDENTE | [BOLETO] Carla — R$ 150.00 (pendente)

Traceback (most recent call last):
  File "main.py", line 24, in <module>
    PagamentoFalho("Diego")
TypeError: Can't instantiate abstract class PagamentoFalho without an implementation for abstract method 'gerar_recibo'

encerramento

O que dominamos hoje

  • O problema: subclasses incompletas passavam despercebidas — bugs silenciosos.
  • A solução: ABC + @abstractmethod transformam métodos em obrigações.
  • A regra: classe abstrata não pode ser instanciada — só herdada.
  • O complemento: métodos concretos e abstratos convivem na mesma classe.
  • Os termos: ABC, método abstrato, método concreto, contrato, interface.
  • Na prática: sistema de pagamentos com contrato formal e detecção de erro em tempo de instanciação.

exercícios · visão geral

Para praticar em casa

Seis exercícios em três níveis. Faça pelo menos um de cada nível antes da próxima aula.

01

Fáceis

Aplicar ABC em hierarquias já conhecidas (Forma, Animal).

02

Médios

Criar abstrações para sistemas reais (notificações, salário).

03

Difíceis

Sistema completo com ABC + interfaces compostas (herança múltipla).

04

Entrega

Suba na pasta aula-10/ do seu repositório do curso.

exercícios · nível fácil

Para fixar a mecânica do ABC

01

Forma 2.0

Refatore Forma da aula 09 usando ABC. Garanta que Circulo, Quadrado e Triangulo implementem area() e perimetro(). Dica: use triângulo equilátero (um único lado) pra simplificar.

02

Animal abstrato

Classe abstrata Animal com método som(). Crie Cachorro, Gato e Vaca. Tente instanciar Animal direto e confirme o TypeError.

exercícios · nível médio

Modelar contratos de sistemas reais

03

Notificações

Abstrata Notificacao com enviar(msg) e formatar(msg). Subclasses: Email, SMS e Push. Cada uma formata a mensagem do seu jeito.

04

Folha de pagamento

Abstrata Funcionario com calcular_salario() abstrato e imprimir_holerite() concreto. Subclasses: CLT, PJ e Estagiario. Use cálculos simples (ex: CLT = base, PJ = base × 1.2, Estagiário = base × 0.5) — o foco é a estrutura ABC, não tributação.

exercícios · nível difícil

Para ir além

05

Personagens de RPG

Abstrata Personagem com nome e vida no __init__, métodos abstratos atacar() e habilidade_especial(), e método concreto descansar() que aumenta vida em 10. Subclasses: Guerreiro, Mago e Arqueiro. Crie a função simular_batalha(grupo) que percorre uma lista de personagens chamando atacar() em cada.

06

Interfaces compostas

Crie Imprimivel (com método imprimir()) e Logavel (com método log()) como abstratas separadas. Crie Pedido(Imprimivel, Logavel) que herda das duas — discuta os trade-offs em sala.

resumo final

Em uma linha cada

  • ABC é a classe-mãe; @abstractmethod é a etiqueta.
  • Classe abstrata não nasce — só vive através das subclasses.
  • Métodos concretos e abstratos podem (e devem) conviver.
  • Polimorfismo + Abstração = código flexível com contrato forte.
  • Quando duvidar, escreva concreto. Promova para abstrato quando aparecer a segunda subclasse.
próxima aula

Aula 11 · 19/05

Exceções e Tratamento de Erros

Vimos hoje o primeiro erro bom: o TypeError que evita que classes incompletas existam. Na próxima aula, aprendemos a criar, lançar e capturar os nossos próprios erros.

try / except Exceções customizadas Hierarquia de erros Boas práticas de erro