PythonProgramação Orientada a Objetos

Programação Orientada a Objetos

Programação Orientada a Objetos em Python

O que é POO?

Imagine que você está construindo uma cidade em um jogo. Você não quer criar cada prédio do zero - seria muito trabalhoso! Em vez disso, você cria "plantas" (classes) e depois constrói vários prédios (objetos) seguindo essa planta.

Programação Orientada a Objetos (POO) é exatamente isso: um paradigma de programação que organiza o código em torno de objetos que possuem características (atributos) e comportamentos (métodos).

Por que usar POO?

  • Reutilização: Crie uma vez, use várias vezes
  • Organização: Código mais limpo e fácil de entender
  • Manutenção: Mais fácil de modificar sem quebrar outras partes
  • Modelagem real: Representa melhor objetos do mundo real

Classes e Objetos

Classe = a planta/blueprint Objeto = a construção real baseada na planta

# Definindo uma classe
class Cachorro:
    pass  # por enquanto, vazia

# Criando objetos (instâncias)
rex = Cachorro()
bob = Cachorro()

print(type(rex))  # <class '__main__.Cachorro'>
print(type(bob))  # <class '__main__.Cachorro'>

O método init e o self

O __init__ é o construtor - ele roda automaticamente quando criamos um objeto. O self se refere ao próprio objeto que está sendo criado.

class Cachorro:
    def __init__(self, nome, idade, raca):
        self.nome = nome
        self.idade = idade
        self.raca = raca

# Criando cachorros com características diferentes
rex = Cachorro("Rex", 3, "Labrador")
bob = Cachorro("Bob", 1, "Vira-lata")

print(rex.nome)   # Rex
print(bob.idade)  # 1

Atributos e Métodos

Atributos = características (variáveis dentro do objeto) Métodos = comportamentos (funções dentro do objeto)

class Cachorro:
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade
        self.energia = 100  # atributo com valor padrão
    
    # Método
    def latir(self):
        print(f"{self.nome} diz: Au au!")
    
    def dormir(self, horas):
        self.energia += horas * 10
        print(f"{self.nome} dormiu {horas} horas. Energia: {self.energia}")
    
    def brincar(self):
        if self.energia >= 20:
            self.energia -= 20
            print(f"{self.nome} está brincando! Energia: {self.energia}")
        else:
            print(f"{self.nome} está cansado demais para brincar!")

# Usando a classe
rex = Cachorro("Rex", 3)
rex.latir()          # Rex diz: Au au!
rex.brincar()        # Rex está brincando! Energia: 80
rex.dormir(2)        # Rex dormiu 2 horas. Energia: 100

Herança

Herança permite criar uma nova classe baseada em uma existente. É como ter uma "planta padrão" e criar versões mais específicas.

# Classe base (pai)
class Animal:
    def __init__(self, nome):
        self.nome = nome
    
    def fazer_som(self):
        print(f"{self.nome} faz algum som")

# Classe derivada (filho)
class Gato(Animal):
    def __init__(self, nome, cor):
        super().__init__(nome)  # chama o __init__ do pai
        self.cor = cor
    
    def fazer_som(self):
        print(f"{self.nome} diz: Miau!")
    
    def ronronar(self):
        print(f"{self.nome} está ronronando...")

class Peixe(Animal):
    def fazer_som(self):
        print(f"{self.nome} faz: Glub glub")

# Usando herança
gato = Gato("Mimi", "preto")
gato.fazer_som()    # Mimi diz: Miau!
gato.ronronar()     # Mimi está ronronando...

peixe = Peixe("Nemo")
peixe.fazer_som()   # Nemo faz: Glub glub

Encapsulamento

Encapsulamento protege dados internos do objeto. Em Python, usamos _ (underline) para indicar que algo é "privado" (por convenção).

class ContaBancaria:
    def __init__(self, titular, saldo_inicial=0):
        self.titular = titular
        self._saldo = saldo_inicial  # "privado" por convenção
        self.__senha = "1234"  # name mangling (mais protegido)
    
    def depositar(self, valor):
        if valor > 0:
            self._saldo += valor
            print(f"Depósito de R${valor:.2f} realizado")
    
    def sacar(self, valor, senha):
        if senha == self.__senha:
            if valor <= self._saldo:
                self._saldo -= valor
                print(f"Saque de R${valor:.2f} realizado")
            else:
                print("Saldo insuficiente!")
        else:
            print("Senha incorreta!")
    
    def ver_saldo(self, senha):
        if senha == self.__senha:
            return f"Saldo: R${self._saldo:.2f}"
        return "Senha incorreta!"

# Exemplo
conta = ContaBancaria("João", 1000)
conta.depositar(500)         # Depósito de R$500.00 realizado
print(conta.ver_saldo("1234"))  # Saldo: R$1500.00
print(conta._saldo)          # 1500 (funciona, mas não é recomendado)
# print(conta.__senha)        # Erro! Atributo "escondido"

Polimorfismo

Polimorfismo = "muitas formas". Permite que objetos de classes diferentes respondam ao mesmo método de formas diferentes.

class Animal:
    def mover(self):
        pass

class Cobra(Animal):
    def mover(self):
        return "Rastejando..."

class Passaro(Animal):
    def mover(self):
        return "Voando..."

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

# Função que usa polimorfismo
def mostrar_movimento(animal):
    print(animal.mover())

# Todos respondem ao mesmo método de forma diferente
animais = [Cobra(), Passaro(), Peixe()]
for animal in animais:
    mostrar_movimento(animal)
# Rastejando...
# Voando...
# Nadando...

Métodos Especiais: str e repr

Estes métodos controlam como nossos objetos são representados como texto.

class Pessoa:
    def __init__(self, nome, idade):
        self.nome = nome
        self.idade = idade
    
    def __str__(self):
        # Usado por print() e str()
        return f"{self.nome} ({self.idade} anos)"
    
    def __repr__(self):
        # Representação técnica, útil para debug
        return f"Pessoa('{self.nome}', {self.idade})"

pessoa = Pessoa("Maria", 30)
print(str(pessoa))   # Maria (30 anos)
print(repr(pessoa))  # Pessoa('Maria', 30)
print(pessoa)        # Maria (30 anos)  - usa __str__ por padrão

Exemplo Prático Completo

Vamos criar um sistema de biblioteca simples:

class Livro:
    def __init__(self, titulo, autor, ano):
        self.titulo = titulo
        self.autor = autor
        self.ano = ano
        self._disponivel = True
    
    def __str__(self):
        status = "Disponível" if self._disponivel else "Emprestado"
        return f"'{self.titulo}' - {self.autor} ({self.ano}) - {status}"
    
    def emprestar(self):
        if self._disponivel:
            self._disponivel = False
            return True
        return False
    
    def devolver(self):
        self._disponivel = True

class Biblioteca:
    def __init__(self, nome):
        self.nome = nome
        self._livros = []
    
    def adicionar_livro(self, livro):
        self._livros.append(livro)
        print(f"Livro '{livro.titulo}' adicionado à biblioteca")
    
    def listar_livros(self):
        if not self._livros:
            print("Biblioteca vazia!")
            return
        print(f"\n=== {self.nome} ===")
        for i, livro in enumerate(self._livros, 1):
            print(f"{i}. {livro}")
    
    def emprestar_livro(self, titulo):
        for livro in self._livros:
            if livro.titulo.lower() == titulo.lower():
                if livro.emprestar():
                    print(f"Livro '{titulo}' emprestado com sucesso!")
                    return
                else:
                    print(f"Livro '{titulo}' já está emprestado!")
                    return
        print(f"Livro '{titulo}' não encontrado!")
    
    def devolver_livro(self, titulo):
        for livro in self._livros:
            if livro.titulo.lower() == titulo.lower():
                livro.devolver()
                print(f"Livro '{titulo}' devolvido!")
                return
        print(f"Livro '{titulo}' não encontrado!")

# Testando o sistema
biblioteca = Biblioteca("Biblioteca Central")

# Criando livros
livro1 = Livro("1984", "George Orwell", 1949)
livro2 = Livro("Dom Casmurro", "Machado de Assis", 1899)
livro3 = Livro("O Pequeno Príncipe", "Antoine de Saint-Exupéry", 1943)

# Adicionando à biblioteca
biblioteca.adicionar_livro(livro1)
biblioteca.adicionar_livro(livro2)
biblioteca.adicionar_livro(livro3)

# Listando livros
biblioteca.listar_livros()

# Emprestando e devolvendo
biblioteca.emprestar_livro("1984")
biblioteca.listar_livros()
biblioteca.emprestar_livro("1984")  # Tentando emprestar de novo
biblioteca.devolver_livro("1984")
biblioteca.listar_livros()

Erros Comuns e Dicas

❌ Erro: Esquecer o self

class Carro:
    def __init__(self, modelo):
        modelo = modelo  # ERRO! Deveria ser self.modelo = modelo

❌ Erro: Confundir classe e instância

class Contador:
    valor = 0  # Atributo de classe

c1 = Contador()
c2 = Contador()
c1.valor += 1
print(c2.valor)  # 0 (não 1!) - cada instância tem seu próprio valor

✅ Dica: Use type hints (dicas de tipo)

class Produto:
    def __init__(self, nome: str, preco: float):
        self.nome = nome
        self.preco = preco
    
    def aplicar_desconto(self, percentual: float) -> float:
        return self.preco * (1 - percentual / 100)

✅ Dica: Herança múltipla com cuidado

class A:
    def metodo(self):
        print("A")

class B:
    def metodo(self):
        print("B")

class C(A, B):  # Ordem importa! A tem prioridade
    pass

c = C()
c.metodo()  # A (primeira na ordem)

Resumo

ConceitoO que fazExemplo
ClasseDefine um tipo de objetoclass Cachorro:
ObjetoInstância de uma classerex = Cachorro()
initConstrutor/inicializadordef __init__(self, nome):
selfReferência ao próprio objetoself.nome = nome
HerançaReutilizar código de outra classeclass Gato(Animal):
EncapsulamentoProteger dados internosself._saldo
PolimorfismoMesmo método, comportamento diferenteanimal.mover()
strRepresentação amigáveldef __str__(self):

POO é uma ferramenta poderosa! Comece com exemplos simples e vá aumentando a complexidade. A prática leva à perfeição! 🚀