Encapsulamento e Herança

Encapsulamento esconde os detalhes internos de um objeto, expondo apenas interfaces operacionais seguras. Herança permite que uma classe tenha automaticamente a funcionalidade de outra, evitando repetição. Estes dois conceitos são a espinha dorsal da programação orientada a objetos.


1. Encapsulamento e Atributos Privados

Encapsulamento significa que os dados internos de um objeto não devem ser acessados diretamente de fora — devem ser manipulados através de métodos.

Sublinhado Simples _ — Convenção para Atributos "Protegidos"

Exemplo: Sublinhado Simples e Duplo

PYTHON
class Pessoa:
    def __init__(self, nome, idade):
        self.nome = nome
        self._idade = idade      # Sublinhado simples: dica de que é interno

p = Pessoa("João Silva", 25)
print(p.nome)               # João Silva — atributo público
print(p._idade)             # 25 — acessível, mas não recomendado
▶ Experimente

Um sublinhado simples é apenas uma convenção, não imposta — Python não impede você de acessá-lo. É como escrever na documentação "isto é interno, não toque."

Sublinhado Duplo __ — Name Mangling

Exemplo: Classe Conta Bancária

PYTHON
class ContaBancaria:
    def __init__(self, titular, saldo):
        self.titular = titular
        self.__saldo = saldo    # Sublinhado duplo: atributo privado
    
    def get_saldo(self):
        """Obtém saldo com segurança"""
        return self.__saldo
    
    def depositar(self, valor):
        if valor > 0:
            self.__saldo += valor
            return True
        return False

conta = ContaBancaria("João Silva", 10000)
print(conta.titular)                # João Silva — público
# print(conta.__saldo)              # Erro! AttributeError
print(conta.get_saldo())            # 10000 — acessado via método
▶ Experimente

Sublinhados duplos acionam name mangling__saldo é internamente transformado para _ContaBancaria__saldo. Isso não é "privacidade" verdadeira, apenas torna menos provável a sobrescrita acidental.

💡 Python não tem verdadeira privacidade. Baseia-se no entendimento "somos todos adultos consentindo" — sublinhado simples significa "uso interno," sublinhado duplo previne colisões de nome. Outras linguagens (como Java) têm uma palavra-chave private verdadeira; Python não tem e não planeja adicionar uma.

Exemplo: Classe Temperatura (Dificuldade ⭐⭐)

PYTHON
class Temperatura:
    def __init__(self, celsius=0):
        self.__celsius = celsius
    
    def to_fahrenheit(self):
        return self.__celsius * 9 / 5 + 32
    
    def set_celsius(self, valor):
        """Define temperatura com segurança — não pode ser abaixo do zero absoluto"""
        if valor < -273.15:
            print("Temperatura não pode ser abaixo do zero absoluto!")
            return
        self.__celsius = valor
    
    def get_celsius(self):
        return self.__celsius

t = Temperatura(100)
print(f"100°C = {t.to_fahrenheit():.1f}°F")   # 212.0°F

t.set_celsius(-300)      # Temperatura não pode ser abaixo do zero absoluto!
t.set_celsius(0)
print(f"0°C = {t.to_fahrenheit():.1f}°F")     # 32.0°F
▶ Experimente

2. @property: Controle Elegante de Acesso

Escrever set_celsius() / get_celsius() é verboso. @property permite controlar o acesso mantendo a sintaxe de acesso a atributos:

Exemplo: Usando @property

PYTHON
class Temperatura:
    def __init__(self, celsius=0):
        self._celsius = celsius
    
    @property
    def celsius(self):
        """Obtém temperatura em Celsius"""
        return self._celsius
    
    @celsius.setter
    def celsius(self, valor):
        """Define temperatura em Celsius — auto-validação"""
        if valor < -273.15:
            raise ValueError("Temperatura não pode ser abaixo do zero absoluto!")
        self._celsius = valor
    
    @property
    def fahrenheit(self):
        """Fahrenheit (somente leitura — sem setter)"""
        return self._celsius * 9 / 5 + 32

t = Temperatura(100)
print(t.celsius)                    # 100 — acessado como atributo
print(t.fahrenheit)                 # 212.0

t.celsius = 0                       # Atribuído como atributo
print(t.celsius)                    # 0

# t.celsius = -300                  # Levantaria ValueError!
# t.fahrenheit = 100                # Erro! Sem setter
▶ Experimente
💡 Vantagens do @property: 1) Sem parênteses para acesso: t.celsius em vez de t.get_celsius(). 2) Validação automática na atribuição: t.celsius = valor em vez de t.set_celsius(valor). 3) Pode converter um atributo regular em um computado sem quebrar código existente.


3. Herança

Herança permite que uma classe filha tenha todos os atributos e métodos de sua classe pai:

Exemplo: Herança Animal

PYTHON
class Animal:
    """Classe base Animal"""
    def __init__(self, nome):
        self.nome = nome
    
    def falar(self):
        return "..."
    
    def comer(self, comida):
        return f"{self.nome} está comendo {comida}"

class Cachorro(Animal):
    """Cachorro — herda de Animal"""
    def falar(self):
        return "Au au!"

class Gato(Animal):
    """Gato — herda de Animal"""
    def falar(self):
        return "Miau!"

dog = Cachorro("Rex")
cat = Gato("Mimi")

print(dog.falar())                  # Au au! (sobrescreve método do pai)
print(cat.falar())                  # Miau! (sobrescreve método do pai)
print(dog.comer("osso"))            # Rex está comendo osso (herdado do pai)
print(cat.comer("peixe"))           # Mimi está comendo peixe (herdado do pai)
▶ Experimente

super(): Chamando Métodos do Pai

Exemplo: super() para Chamar o Pai

PYTHON
class Animal:
    def __init__(self, nome):
        self.nome = nome

class Cachorro(Animal):
    def __init__(self, nome, raca):
        super().__init__(nome)      # Chama o __init__ do pai
        self.raca = raca           # Adiciona atributo específico do filho
    
    def apresentar(self):
        return f"Sou {self.nome}, um {self.raca}"

dog = Cachorro("Rex", "Golden Retriever")
print(dog.apresentar())              # Sou Rex, um Golden Retriever
▶ Experimente

4. Padrão Mixin

Um Mixin é uma classe especial — não usada independentemente, mas "misturada" para adicionar funcionalidade a outras classes:

Exemplo: Padrão Mixin

PYTHON
class MixinVoador:
    """Mixin de habilidade de voo"""
    def voar(self):
        return f"{self.nome} está voando!"

class MixinNadador:
    """Mixin de habilidade de natação"""
    def nadar(self):
        return f"{self.nome} está nadando!"

class Animal:
    def __init__(self, nome):
        self.nome = nome

class Pato(Animal, MixinNadador, MixinVoador):
    """Pato — pode nadar e voar"""
    def falar(self):
        return "Quack!"

class Peixe(Animal, MixinNadador):
    """Peixe — só pode nadar"""
    def falar(self):
        return "..."

pato = Pato("Donald")
peixe = Peixe("Nemo")

print(pato.falar())                 # Quack!
print(pato.nadar())                 # Donald está nadando!
print(pato.voar())                  # Donald está voando!
print(peixe.nadar())                # Nemo está nadando!
▶ Experimente
💡 Convenção de nomenclatura de Mixin: Nomes de classe terminando em Mixin indicam que são para misturar, não para uso independente. Uma classe pode misturar múltiplos Mixins mais uma classe base principal — muito mais flexível que herança única profunda.


Casos de Uso Comuns


❓ Perguntas Frequentes

P: Quando devo usar herança vs. Mixin? R: Herança é adequada para relacionamentos "é-um" — um cachorro é um animal. Mixins são adequados para "tem a habilidade" — um pato pode nadar. Prefira composição (Mixin) sobre herança, pois hierarquias de herança profundas são difíceis de manter. Uma boa regra: herança não deve exceder 3 níveis. P: Qual a diferença entre @property e um método regular? R: @property faz um método parecer um atributo — sem parênteses para acesso, atribuição aciona o setter. É essencialmente um "interceptador de atributos" — você escreve código como se estivesse manipulando um atributo, mas um método realmente executa. P: Python suporta herança múltipla? Alguma armadilha? R: Sim — class C(A, B). No entanto, herança múltipla tem o "problema do diamante" (qual método do pai usar quando ambos têm o mesmo nome). Python usa MRO (Method Resolution Order), pesquisando da esquerda para a direita. Mixins são um padrão de design que aproveita a herança múltipla, com classes Mixin sendo pequenas e funcionalmente independentes, evitando problemas de diamante.


📖 Resumo


📝 Atividades

  1. Iniciante (Dificuldade ⭐): Defina uma classe Produto com @property para o atributo preco — deve ser positivo ao definir; retorna o valor original ao obter.

  2. Intermediário (Dificuldade ⭐⭐): Defina uma herança de três níveis: Animal -> Mamifero -> Cachorro / Gato. Animal tem nome e idade; Mamifero adiciona cor_pelo; Cachorro e Gato implementam seus próprios métodos falar().

  3. Avançado (Dificuldade ⭐⭐⭐): Crie um sistema de logging com MixinLogavel (fornece métodos log_info(), log_error()) e uma classe base Aplicacao (tem um atributo nome). Faça WebApp e CLIApp herdarem de Aplicacao e misturarem MixinLogavel para implementar funcionalidade de logging.

100%