9. Programação Orientada a Objetos (POO)#

Chegamos a um dos conceitos mais transformadores da computação moderna. Se até agora você aprendeu a criar ferramentas isoladas (Funções) e a organizar gavetas de dados (Listas e Dicionários), a Programação Orientada a Objetos (POO) ensinará você a construir sistemas inteligentes.

A POO é um paradigma que permite estruturar o código de maneira mais próxima de como enxergamos o mundo real: focado em “coisas” (Objetos) e em como elas interagem entre si, unindo dados e ações em um só lugar.

Classes vs. Objetos#

Para entender a diferença de forma simples, esqueça a tecnologia por um momento e pense em uma Forma de fazer biscoitos.

  • Classe (O Molde): A classe é a forma de metal. Ela define o formato (digamos, um boneco ou uma estrela) e dita as regras do que o biscoito vai ser. No entanto, você não pode comer a forma. Ela não é o biscoito real, é apenas o molde que usamos para criá-lo.

  • Objeto (O Biscoito Real): O objeto é o biscoito que saiu do forno. Usando aquela única forma de metal (a Classe), você pode criar dezenas de biscoitos reais (Objetos). E o mais legal: um biscoito pode ter cobertura de chocolate, outro de morango e outro de baunilha. Cada biscoito é único e tem suas próprias características (Atributos), mas todos foram criados a partir do mesmo molde.

Na programação, chamamos o ato de “assar um novo biscoito usando a forma” de Instanciar um Objeto.

Outro exemplo prático é um Formulário de Cadastro em branco:

  • O papel em branco com os campos “Nome” e “Idade” é a Classe.

  • O papel preenchido com “Maria, 25 anos” é o Objeto.

Criando sua Primeira Classe#

No Python, utilizamos a palavra reservada class (com a primeira letra sempre maiúscula por convenção) para definir o nosso molde.

O Construtor (__init__) e o self#

Toda classe costuma ter um método especial chamado __init__ (inicializador). Ele é executado automaticamente assim que o objeto “nasce”.

A palavra self é obrigatória e representa “este objeto em particular”. É a forma de a classe dizer: “estou guardando esta cor neste carro específico que acabei de criar”.

class Carro:
    # O método __init__ inicializa as características (Atributos)
    def __init__(self, marca, modelo, cor):
        self.marca = marca     # Atributo (Dado / Estado)
        self.modelo = modelo   # Atributo
        self.cor = cor         # Atributo
        self.velocidade = 0    # Valor padrão
        
    # As ações que o objeto pode fazer (Métodos / Comportamentos)
    def acelerar(self, incremento):
        self.velocidade += incremento
        print(f"O {self.modelo} acelerou. Velocidade: {self.velocidade} km/h")

Instanciando e Usando Objetos#

Agora que temos o “projeto” do carro, vamos fabricar (instanciar) dois carros diferentes e testar suas funções.

# Criando os objetos (Instanciação)
carro1 = Carro("Toyota", "Corolla", "Prata")
carro2 = Carro("Ford", "Mustang", "Vermelho")

# Acessando os atributos e métodos
print(f"Meu carro é um {carro1.marca} da cor {carro1.cor}.")
carro1.acelerar(30)
carro2.acelerar(80) # Cada objeto mantém sua própria velocidade de forma independente
Meu carro é um Toyota da cor Prata.
O Corolla acelerou. Velocidade: 30 km/h
O Mustang acelerou. Velocidade: 80 km/h

Os Quatro Pilares da POO#

Se criar Classes e Objetos é o vocabulário básico, os Quatro Pilares são as leis da física desse nosso universo virtual. Eles definem como os objetos devem ser construídos para garantir segurança, reutilização e flexibilidade.

Encapsulamento#

É a prática de esconder os detalhes internos de funcionamento de um objeto e proteger seus dados contra modificações indevidas feitas de fora da classe. Imagine o motor de um carro: você interage com o pedal (interface), mas os pistões e as correias ficam escondidos e protegidos sob o capô.

Em Python, indicamos que um atributo não deve ser acessado diretamente usando underscores (_ ou __):

class ContaBancaria:
    def __init__(self, titular, saldo_inicial):
        self.titular = titular
        # O duplo underscore indica que este atributo é "privado"
        self.__saldo = saldo_inicial 

    def depositar(self, valor):
        if valor > 0:
            self.__saldo += valor

    def exibir_saldo(self):
        # O saldo só pode ser lido de forma controlada através deste método
        print(f"Saldo: R$ {self.__saldo}")

conta = ContaBancaria("Ana", 1000)
conta.depositar(500)
conta.exibir_saldo() # Correto.
# print(conta.__saldo) -> Erro! O Python protege o acesso direto a este atributo.
Saldo: R$ 1500

Herança#

A Herança permite criar uma nova classe (Subclasse / Filha) baseada em uma classe já existente (Superclasse / Pai). A classe filha herda os atributos e métodos do pai, podendo adicionar coisas novas. Isso evita que você tenha que reescrever código do zero.

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

    def comer(self):
        print(f"{self.nome} está se alimentando.")

# Subclasse (Filha) - Recebe (Animal) entre parênteses
class Cachorro(Animal):
    def latir(self):
        print("Au au!")

rex = Cachorro("Rex")
rex.comer() # Método herdado da classe pai
rex.latir() # Método exclusivo da classe filha
Rex está se alimentando.
Au au!

Polimorfismo#

Polimorfismo significa que objetos de classes diferentes podem responder ao mesmo comando (método) de formas diferentes. Geralmente, ocorre quando as classes filhas sobrescrevem um método do pai para adaptá-lo às suas próprias necessidades.

class Passaro(Animal):
    def mover(self):
        return "Voando pelo céu."

class Peixe(Animal):
    def mover(self):
        return "Nadando no oceano."

# Lista com objetos de tipos diferentes
animais = [Passaro("Piu"), Peixe("Nemo")]

# O mesmo comando (.mover()) gera resultados específicos para cada objeto
for animal in animais:
    print(f"{animal.nome} está {animal.mover()}")
Piu está Voando pelo céu.
Nemo está Nadando no oceano.

Abstração#

A Abstração significa focar apenas nas características essenciais de um objeto e esconder a complexidade (“o como” ele faz as coisas) por trás de comandos simples e diretos.

Pense no exemplo do nosso Carro: quando você senta no banco do motorista e gira a chave, o carro liga. Você não precisa saber como o motor injeta o combustível, como as velas geram a faísca ou como a bateria distribui a energia. O carro abstrai toda essa engenharia complexa e te entrega apenas uma interface simples: a chave na ignição.

Na programação, fazemos a mesma coisa. Criamos classes com métodos simples para quem vai usar o código, enquanto escondemos as regras difíceis em métodos e atributos privados (usando o Encapsulamento que vimos acima).

Veja como podemos abstrair a lógica de ligar um carro no código:

class Carro:
    def __init__(self, modelo):
        self.modelo = modelo
        self.__combustivel = 100  # Atributo privado

    # Métodos privados: A engenharia interna que o motorista não precisa ver
    def __injetar_combustivel(self):
        self.__combustivel -= 2
        
    def __gerar_faisca(self):
        pass # Lógica complexa do motor...

    # Método público: A "chave" que o motorista usa (A Abstração)
    def ligar(self):
        if self.__combustivel > 0:
            self.__injetar_combustivel()
            self.__gerar_faisca()
            print(f"Vrum! O {self.modelo} está ligado e pronto para andar.")
        else:
            print("Sem combustível!")

meu_carro = Carro("Mustang")

# O usuário só precisa chamar um comando simples:
meu_carro.ligar() 

# Ele não precisa (e nem deve) chamar meu_carro.__injetar_combustivel()
Vrum! O Mustang está ligado e pronto para andar.

Neste exemplo, o método ligar() é a nossa abstração. Quem usa a classe Carro só precisa saber que esse método existe, sem precisar se preocupar com as etapas internas de injeção e faísca. Isso torna o código extremamente fácil de usar e diminui as chances de erros!

🎉 Parabéns! Você finalizou a introdução à Programação Orientada a Objetos. Compreender classes, objetos e os quatro pilares muda a forma como modelamos problemas, permitindo criar sistemas mais robustos e profissionais.