Classes abstratas servem como modelos para outras classes.
Elas não permitem instanciação direta pelo programador.
Primeiramente, use from abc import ABC, abstractmethod.
Por exemplo, class Forma(ABC): define uma classe abstrata.
Além disso, @abstractmethod força a implementação nas filhas.
Assim, você garante uma interface consistente em toda hierarquia.
Consequentemente, o código fica mais confiável e previsível.
Quando utilizar classes abstratas? Em hierarquias de classes bem definidas.
Também quando você quer garantir uma interface consistente.
Por outro lado, para classes simples e isoladas, não são necessárias.
Python oferece o módulo abc para essa funcionalidade.
Então, vamos explorar conceitos, implementação e boas práticas.
Três subtítulos guiarão você pelo mundo das classes abstratas.
Portanto, ao final, você projetará hierarquias robustas e consistentes.
Criando classes abstratas com abc
Uma classe abstrata herda de ABC (Abstract Base Class).
Métodos abstratos usam o decorador @abstractmethod.
Classes filhas devem implementar todos os métodos abstratos.
Quando usar essa abordagem? Em frameworks e bibliotecas.
Também para definir contratos entre equipes diferentes.
Exemplo de classe abstrata:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
from abc import ABC, abstractmethod import math class Forma(ABC): """Classe abstrata para todas as formas geométricas.""" @abstractmethod def area(self): """Calcula a área da forma (deve ser implementado).""" pass @abstractmethod def perimetro(self): """Calcula o perímetro da forma (deve ser implementado).""" pass def descricao(self): """Método concreto (não precisa ser sobrescrito).""" return f"Área: {self.area():.2f}, Perímetro: {self.perimetro():.2f}" # Tentar instanciar classe abstrata gera erro try: f = Forma() except TypeError as e: print(f"Erro: {e}") # Classes concretas (implementam os métodos abstratos) class Retangulo(Forma): def __init__(self, largura, altura): self.largura = largura self.altura = altura def area(self): return self.largura * self.altura def perimetro(self): return 2 * (self.largura + self.altura) class Circulo(Forma): def __init__(self, raio): self.raio = raio def area(self): return math.pi * self.raio ** 2 def perimetro(self): return 2 * math.pi * self.raio class Triangulo(Forma): def __init__(self, base, altura, lado1, lado2, lado3): self.base = base self.altura = altura self.lado1 = lado1 self.lado2 = lado2 self.lado3 = lado3 def area(self): return (self.base * self.altura) / 2 def perimetro(self): return self.lado1 + self.lado2 + self.lado3 # Demonstração print("=== Classes Abstratas ===\n") formas = [ Retangulo(5, 3), Circulo(4), Triangulo(6, 4, 3, 4, 5) ] for forma in formas: print(f"{forma.__class__.__name__}: {forma.descricao()}") # Verificação de tipos print(f"\nRetangulo é subclasse de Forma? {issubclass(Retangulo, Forma)}") print(f"Circulo é instância de Forma? {isinstance(Circulo(2), Forma)}") |
Classes abstratas garantem que todas as filhas tenham a mesma interface. Elas funcionam como contratos formais no seu código. Portanto, use-as para evitar erros de implementação.
Métodos abstratos com implementação parcial
Classes abstratas podem ter métodos concretos também.
Métodos abstratos podem ter implementação (chamável com super()).
Isso permite reaproveitar código comum entre as filhas.
Quando usar essa abordagem? Em padrões Template Method.
Também para fornecer comportamento padrão opcional.
Assim, você evita duplicação desnecessária de código.
Exemplo de classe abstrata com implementação parcial:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
from abc import ABC, abstractmethod class Animal(ABC): """Classe abstrata com implementação parcial.""" def __init__(self, nome): self.nome = nome @abstractmethod def fazer_som(self): """Cada animal deve implementar seu som.""" pass @abstractmethod def mover(self): """Cada animal deve implementar seu movimento.""" pass def apresentar(self): """Método concreto que usa métodos abstratos.""" return f"{self.nome} diz '{self.fazer_som()}' e {self.mover()}" def __str__(self): return f"Animal: {self.nome}" # Classes concretas class Cachorro(Animal): def fazer_som(self): return "Au au!" def mover(self): return "corre pelo parque" class Gato(Animal): def fazer_som(self): return "Miau!" def mover(self): return "anda silenciosamente" class Passaro(Animal): def fazer_som(self): return "Piu piu!" def mover(self): return "voa pelo céu" # Exemplo com método abstrato que tem implementação class Veiculo(ABC): @abstractmethod def ligar(self): """Liga o veículo (pode ser chamado via super()).""" print("Veículo: verificando sistemas básicos") @abstractmethod def desligar(self): print("Veículo: desligando sistemas") class Carro(Veiculo): def ligar(self): super().ligar() # Chama implementação da classe mãe print("Carro: motor ligado, pronto para dirigir") def desligar(self): super().desligar() print("Carro: motor desligado, chave removida") class Moto(Veiculo): def ligar(self): super().ligar() print("Moto: partida elétrica acionada") def desligar(self): super().desligar() print("Moto: cortando ignição") # Demonstração print("=== Implementação Parcial ===\n") animais = [Cachorro("Rex"), Gato("Mimi"), Passaro("Piu")] for animal in animais: print(animal.apresentar()) print("\n=== Veículos com super() ===") carro = Carro() moto = Moto() print("Ligando carro:") carro.ligar() print("\nDesligando carro:") carro.desligar() print("\nLigando moto:") moto.ligar() print("\nDesligando moto:") moto.desligar() |
Métodos abstratos com implementação base são úteis e flexíveis. Eles permitem extensão sem duplicação de código. Portanto, use essa técnica para bases sólidas.
Propriedades abstratas e verificação de interface
Propriedades também podem ser declaradas como abstratas.
Use @property e @abstractmethod juntos.
Isso força as classes filhas a implementarem getters e setters.
Quando usar propriedades abstratas? Em atributos obrigatórios.
Também para garantir que certos dados estejam disponíveis.
Além disso, a verificação ocorre em tempo de instanciação.
Exemplo de propriedades abstratas:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
from abc import ABC, abstractmethod class Funcionario(ABC): """Classe abstrata com propriedades abstratas.""" def __init__(self, nome): self.nome = nome @property @abstractmethod def salario(self): """Salário do funcionário (propriedade abstrata).""" pass @property @abstractmethod def cargo(self): """Cargo do funcionário (propriedade abstrata).""" pass @abstractmethod def trabalhar(self): """Realiza o trabalho específico.""" pass def exibir_informacoes(self): return f"{self.cargo}: {self.nome} - R${self.salario:.2f}" class Desenvolvedor(Funcionario): def __init__(self, nome, salario_base, bonus=0): super().__init__(nome) self._salario_base = salario_base self._bonus = bonus @property def salario(self): return self._salario_base + self._bonus @property def cargo(self): return "Desenvolvedor" def trabalhar(self): return f"{self.nome} está programando em Python" class Gerente(Funcionario): def __init__(self, nome, salario_base, equipe_size): super().__init__(nome) self._salario_base = salario_base self._equipe_size = equipe_size @property def salario(self): return self._salario_base + (self._equipe_size * 500) @property def cargo(self): return "Gerente" def trabalhar(self): return f"{self.nome} está gerenciando {self._equipe_size} pessoas" class Estagiario(Funcionario): def __init__(self, nome, bolsa): super().__init__(nome) self._bolsa = bolsa @property def salario(self): return self._bolsa @property def cargo(self): return "Estagiário" def trabalhar(self): return f"{self.nome} está aprendendo e ajudando" # Demonstração print("=== Propriedades Abstratas ===\n") funcionarios = [ Desenvolvedor("Ana", 5000, 1000), Gerente("Carlos", 8000, 5), Estagiario("Mariana", 1500) ] for func in funcionarios: print(func.exibir_informacoes()) print(f" Trabalho: {func.trabalhar()}") print() # Verificação de interface (todos implementam os métodos) def processar_funcionario(func: Funcionario): """Função que aceita qualquer Funcionário (polimorfismo).""" print(f"Processando: {func.exibir_informacoes()}") print("=== Processamento Polimórfico ===") processar_funcionario(Desenvolvedor("João", 6000, 500)) # Exemplo de verificação de tipo abstrato from abc import ABCMeta class Plugin(metaclass=ABCMeta): """Outra forma de declarar classe abstrata (alternativa).""" @abstractmethod def executar(self, dados): pass @abstractmethod def nome(self): pass class PluginA(Plugin): def executar(self, dados): return f"PluginA processou: {dados}" def nome(self): return "Plugin A" print("\n=== Plugin com metaclass ===") plugin = PluginA() print(f"Plugin: {plugin.nome()}") print(plugin.executar("dados importantes")) |
Propriedades abstratas garantem que atributos essenciais existam. A fórmula de uma classe abstrata bem projetada: \(C_{\text{abstrata}} = \text{interface} + \text{implementação comum}\) Classes abstratas são ferramentas poderosas para arquitetura de software. Use-as para definir contratos claros e evitar duplicação. Portanto, seu sistema ficará mais robusto e fácil de estender. Finalmente, pratique com exemplos reais do seu dia a dia.