Aula 14: Classes Abstratas

Definindo contratos para suas hierarquias de classes.

O Que Você Vai Aprender

Nesta aula, você explorará o conceito de Classes Abstratas, uma ferramenta poderosa da Programação Orientada a Objetos para definir interfaces e contratos em suas hierarquias de classes. Você aprenderá a utilizar o módulo `abc` do Python para criar classes e métodos abstratos, garantindo que as subclasses implementem comportamentos específicos.

📝 Conceito de Classes Abstratas

Compreender o que é um "contrato" de classe e por que não é possível instanciá-las diretamente.

📦 Módulo `abc` em Python

Aprender a usar `ABC` e `@abstractmethod` para definir abstrações.

Conceito: Classes Abstratas

Uma Classe Abstrata é uma classe que não pode ser instanciada diretamente. Ela serve como um "contrato" ou um "esqueleto" para outras classes. O principal objetivo é definir um conjunto de métodos que suas subclasses são obrigadas a implementar.

Por que usar Classes Abstratas?

  • **Forçar Implementação:** Garante que todas as subclasses sigam um padrão e implementem os métodos essenciais.
  • **Definir Interfaces:** Cria uma interface comum para um grupo de classes relacionadas, mesmo que a implementação de cada método seja diferente.
  • **Reutilização de Código:** Classes abstratas podem ter métodos concretos (não abstratos) que são herdados pelas subclasses.
  • **Melhorar a Organização:** Ajuda a modelar sistemas complexos de forma mais clara e estruturada.

Métodos Abstratos

Um método abstrato é um método declarado em uma classe abstrata, mas que não possui implementação na própria classe abstrata. Ele serve como um placeholder que DEVE ser implementado por qualquer subclasse concreta que herdar dela.

# Exemplo conceitual (não Python real ainda)
class ClasseAbstrata:
    def metodo_abstrato(self):
        # Nenhuma implementação aqui
        pass 

class ClasseConcreta(ClasseAbstrata):
    def metodo_abstrato(self):
        # Implementação OBRIGATÓRIA aqui
        print("Método abstrato implementado!")

# obj_abstrato = ClasseAbstrata() # Isso não seria permitido!
# obj_concreto = ClasseConcreta() # Isso seria permitido!

Módulo `abc`: Criando Abstrações em Python

Em Python, não temos classes abstratas "nativas" como em algumas outras linguagens (ex: Java, C++). Para criar classes abstratas, utilizamos o módulo embutido `abc` (Abstract Base Classes).

Como Usar o Módulo `abc`

Você precisa importar `ABC` (Abstract Base Class) e `abstractmethod` de `abc`:

from abc import ABC, abstractmethod

Para tornar uma classe abstrata, ela deve herdar de `ABC`.

Para definir um método como abstrato, use o decorador `@abstractmethod` sobre o método. Esse método não terá corpo na classe abstrata, apenas `pass`.

Exemplo Prático: `FormaGeometrica`

Vamos criar uma classe abstrata `FormaGeometrica` que define um método `calcular_area()` abstrato. Em seguida, implementaremos subclasses concretas como `Circulo` e `Retangulo` que serão obrigadas a fornecer sua própria lógica para calcular a área.

Classes `FormaGeometrica`, `Circulo` e `Retangulo`

from abc import ABC, abstractmethod
import math

class FormaGeometrica(ABC): # Herda de ABC
    @abstractmethod # Decorador para método abstrato
    def calcular_area(self):
        pass # Nenhuma implementação aqui, apenas um contrato

    def exibir_info(self): # Método concreto (não abstrato)
        print("Esta é uma forma geométrica.")

class Circulo(FormaGeometrica):
    def __init__(self, raio):
        self.raio = raio

    def calcular_area(self): # Implementação obrigatória para Circulo
        return math.pi * (self.raio ** 2)

class Retangulo(FormaGeometrica):
    def __init__(self, largura, altura):
        self.largura = largura
        self.altura = altura

    def calcular_area(self): # Implementação obrigatória para Retangulo
        return self.largura * self.altura

# Exemplo de uso:
# # forma = FormaGeometrica() # Isso geraria um TypeError! Não pode ser instanciada.

# circulo = Circulo(5)
# print(f"Área do Círculo: {circulo.calcular_area():.2f}")
# circulo.exibir_info() # Método concreto herdado

# retangulo = Retangulo(4, 6)
# print(f"Área do Retângulo: {retangulo.calcular_area():.2f}")
# retangulo.exibir_info() # Método concreto herdado

Simulador: Formas Geométricas Abstratas

Experimente criar instâncias de `Circulo` e `Retângulo` e calcular suas áreas. Tente instanciar `FormaGeometrica` para ver o erro!

Classes Abstratas (Simuladas)

Aqui, simulamos o comportamento de um método abstrato forçando um erro se ele não for sobrescrito, e impedindo a instanciação direta da classe base.

// Simulação de classes abstratas 
# Importa as ferramentas necessárias do módulo de Classes Abstratas (abc)
from abc import ABC, abstractmethod
import math

class FormaGeometrica(ABC):
    """
    Esta é uma Classe Abstrata (Abstract Base Class - ABC).
    Ela serve como um "contrato" que define métodos que suas subclasses
    são obrigadas a implementar.
    Não é possível criar um objeto diretamente desta classe.
    """
    @abstractmethod
    def calcular_area(self):
        """
        Este é um método abstrato. Qualquer classe que herdar de
        FormaGeometrica DEVE implementar seu próprio método calcular_area.
        """
        pass

class Circulo(FormaGeometrica):
    """
    Subclasse concreta que herda de FormaGeometrica.
    """
    def __init__(self, raio):
        self.raio = raio
    
    def calcular_area(self):
        """
        Implementação obrigatória e específica do método abstrato.
        """
        return math.pi * (self.raio ** 2)

class Retangulo(FormaGeometrica):
    """
    Outra subclasse concreta que herda de FormaGeometrica.
    """
    def __init__(self, largura, altura):
        self.largura = largura
        self.altura = altura
    
    def calcular_area(self):
        """
        Implementação obrigatória e específica do método abstrato.
        """
        return self.largura * self.altura
# 1. Criando instâncias das classes concretas
circulo = Circulo(10)
retangulo = Retangulo(5, 4)

print(f"Área do Círculo: {circulo.calcular_area():.2f}")
print(f"Área do Retângulo: {retangulo.calcular_area()}")

# 2. O que acontece se tentarmos instanciar a classe abstrata?
try:
    forma = FormaGeometrica()
except TypeError as e:
    print(f"\nErro ao tentar instanciar a classe abstrata: {e}")

# 3. O que acontece se uma subclasse não implementar o método abstrato?
class Triangulo(FormaGeometrica):
    def __init__(self, base, altura):
        self.base = base
        self.altura = altura
    # OBS: Note que não implementamos o método 'calcular_area()'

try:
    triangulo = Triangulo(10, 5)
except TypeError as e:
    print(f"Erro ao tentar instanciar uma subclasse incompleta: {e}")

Criar e Testar Formas

Desafios para Continuar

Coloque seus conhecimentos em prática com estes desafios para solidificar sua compreensão sobre classes abstratas.

  • 1. Abstraindo um `MeioDePagamento`

    Crie uma classe abstrata `MeioDePagamento` com um método abstrato `processar_pagamento(valor)`.

    Crie subclasses `CartaoDeCredito` e `BoletoBancario` que herdem de `MeioDePagamento` e implementem `processar_pagamento()` de forma específica para cada tipo de pagamento.

  • 2. Classe Abstrata `FuncionarioBase`

    Crie uma classe abstrata `FuncionarioBase` com atributos `nome` e `salario` e um método concreto `exibir_dados()`.

    Adicione um método abstrato `calcular_bonus()` em `FuncionarioBase`. Crie subclasses `Desenvolvedor` e `Gerente` que implementem `calcular_bonus()` de maneira diferente (ex: Desenvolvedor = 10% do salário, Gerente = 20% do salário).