🛡️ 9.1 Encapsulamento e Propriedades

🎯 Objetivo

Aprender a proteger os dados de uma classe utilizando o conceito de Encapsulamento e a utilizar Properties (Propriedades) para criar interfaces limpas e profissionais no Python.

🏗️ O Conceito

Encapsular significa esconder os detalhes internos de uma classe e expor apenas o necessário. Isso é fundamental para que o sistema suporte mudanças sem quebrar: se mudarmos uma regra de negócio, alteramos apenas um lugar (dentro da classe), e não em todos os arquivos que a utilizam.

O Problema do Acesso Direto

No Python, a convenção para sinalizar que um atributo é “privado” ou protegido é prefixá-lo com um underscore (_). Isso alerta outros desenvolvedores de que aquele atributo não deve ser acessado diretamente.

class Conta:
    def __init__(self, titular: str, saldo: float):
        self._titular = titular  # Atributo protegido
        self._saldo = saldo      # Atributo protegido

💻 Mão na Massa

Passo 1: O Estilo Java (Getters e Setters)

Muitas linguagens (como Java) utilizam métodos explícitos para acessar dados. Embora funcione, o código fica verboso e “feio”.

class Conta:
    def __init__(self, saldo: float):
        self._saldo = saldo
 
    def get_saldo(self) -> float:
        return self._saldo
 
    def set_saldo(self, valor: float) -> None:
        if valor < 0:
            print("Erro: O saldo não pode ser negativo.")
        else:
            self._saldo = valor
 
# Uso "feio":
conta = Conta(100.0)
conta.set_saldo(conta.get_saldo() + 50.0)

Passo 2: O Estilo Pythônico (Properties)

O Python oferece uma solução elegante chamada Properties. Elas permitem que você acesse um método como se fosse um atributo público, mas mantendo a lógica de validação por trás.

class Conta:
    def __init__(self, saldo: float = 0.0):
        self._saldo = saldo
 
    @property
    def saldo(self) -> float:
        """Getter da propriedade saldo"""
        return self._saldo
 
    @saldo.setter
    def saldo(self, valor: float) -> None:
        """Setter da propriedade saldo com validação"""
        if valor < 0:
            print("🛑 Alerta: O saldo não pode ser negativo!")
        else:
            self._saldo = valor
 
# Uso limpo e elegante:
conta = Conta(1000.0)
conta.saldo = 500.0  # Chama o @saldo.setter automaticamente
print(f"Saldo atual: R$ {conta.saldo:.2f}") # Chama o @property

Passo 3: Quando usar Setters?

Nem todo atributo precisa de um setter. Na verdade, para uma conta bancária, o saldo só deveria mudar através de métodos de negócio como depositar() e sacar().

class Conta:
    def __init__(self, saldo: float):
        self._saldo = saldo
 
    @property
    def saldo(self) -> float:
        return self._saldo
 
    def depositar(self, valor: float) -> None:
        self._saldo += valor
 
    def sacar(self, valor: float) -> None:
        if valor <= self._saldo:
            self._saldo -= valor
        else:
            print("❌ Saldo insuficiente.")

✅ Resultado Esperado

Ao utilizar Properties, sua classe terá uma interface limpa. O código conta.saldo = -100 acionará a validação imediatamente, protegendo a integridade do objeto sem a necessidade de parênteses extras.

Regra de Ouro

Só crie um @property.setter se você realmente precisar permitir a alteração direta do valor. Caso contrário, use apenas @property (leitura) e métodos de negócio para modificação.

🚨 Erros Comuns

ErroCausaSolução
RecursionErrorChamar self.saldo = valor dentro do setter def saldo.O atributo interno deve ter um nome diferente, geralmente self._saldo.
Esquecer o @propertyTentar usar o setter sem o getter definido.O @property deve vir antes do @nome.setter.

🔗 Próximo Capítulo

Agora que você domina o encapsulamento, vamos explorar os Atributos de Classe.


⬅️ Capítulo Anterior | Próximo Capítulo ➡️