Procedural x Poo
Comparando Aplicativos: Procedural vs. Orientado a Objetos¶
Entendendo os Paradigmas
Antes de começarmos a criar os aplicativos, vamos entender brevemente a diferença entre programação procedural e orientada a objetos:
- Programação Procedural: Foca em uma sequência de passos para realizar uma tarefa. O código é organizado em funções que realizam ações específicas.
- Programação Orientada a Objetos: Organiza o código em objetos que representam entidades do mundo real. Cada objeto possui atributos (características) e métodos (ações).
Exemplo: Cálculo de Área de um Retângulo
Vamos criar um aplicativo simples que calcula a área de um retângulo usando ambos os paradigmas.
### 1. Programação Procedural
def calcular_area(base, altura):
"""Calcula a área de um retângulo.
Args:
base (float): A base do retângulo.
altura (float): A altura do retângulo.
Returns:
float: A área do retângulo.
"""
area = base * altura
return area
# Obtendo os dados do usuário
base = float(input("Digite a base do retângulo: "))
altura = float(input("Digite a altura do retângulo: "))
# Calculando a área
resultado = calcular_area(base, altura)
# Imprimindo o resultado
print("A área do retângulo é:", resultado)
A área do retângulo é: 4.0
### 2. Programação Orientada a Objetos
class Retangulo:
def __init__(self, base, altura):
self.base = base
self.altura = altura
def calcular_area(self):
return self.base * self.altura
# Obtendo os dados do usuário
base = float(input("Digite a base do retângulo: "))
altura = float(input("Digite a altura do retângulo: "))
# Criando um objeto Retangulo
retangulo = Retangulo(base, altura)
# Calculando a área
resultado = retangulo.calcular_area()
# Imprimindo o resultado
print("A área do retângulo é:", resultado)
A área do retângulo é: 4.0
Analisando os Exemplos
- Procedural: O código é organizado em uma função
calcular_area
que realiza o cálculo. Os dados são passados como argumentos para a função. - Orientado a Objetos: Criamos uma classe
Retangulo
que representa um retângulo. A classe possui atributosbase
ealtura
e um métodocalcular_area
para calcular a área.
Quando usar cada paradigma?
- Procedural: Ideal para tarefas simples e diretas, onde a organização do código em funções é suficiente.
- Orientado a Objetos: Mais adequado para sistemas complexos, onde a organização do código em objetos facilita a manutenção e reutilização.
Outras Considerações
- Encapsulamento: Na POO, os atributos e métodos podem ser públicos ou privados, controlando o acesso aos dados.
- Herança: Classes podem herdar atributos e métodos de outras classes, promovendo a reutilização de código.
- Polimorfismo: Objetos de diferentes classes podem responder de forma diferente ao mesmo método.
Aplicativos Mais Complexos
Para aplicativos mais complexos, como jogos, interfaces gráficas ou sistemas de gerenciamento de dados, a POO geralmente é a abordagem preferida devido à sua capacidade de modelar o mundo real de forma mais natural.
Exercícios
- Expanda o exemplo: Crie uma classe
Circulo
com atributosraio
e um método para calcular a área. - Implemente um jogo simples: Crie um jogo de adivinhação usando classes para representar o jogador, o computador e o jogo em si.
# Crie uma interface gráfica:** Utilize uma biblioteca como Kyvi para criar uma interface gráfica para o cálculo da área do retângulo.
! pip install "kivy[base]" kivy_examples
Collecting kivy_examples Using cached Kivy_examples-2.3.0-py2.py3-none-any.whl.metadata (13 kB) Collecting kivy[base] Using cached Kivy-2.3.0-cp310-cp310-win_amd64.whl.metadata (16 kB) Collecting Kivy-Garden>=0.1.4 (from kivy[base]) Using cached Kivy_Garden-0.1.5-py3-none-any.whl.metadata (159 bytes) Collecting docutils (from kivy[base]) Using cached docutils-0.21.2-py3-none-any.whl.metadata (2.8 kB) Requirement already satisfied: pygments in f:\apps\source\repos\24.2\pbe_24.2_8001\.venv\lib\site-packages (from kivy[base]) (2.18.0) Collecting kivy-deps.angle~=0.4.0 (from kivy[base]) Using cached kivy_deps.angle-0.4.0-cp310-cp310-win_amd64.whl.metadata (206 bytes) Collecting kivy-deps.sdl2~=0.7.0 (from kivy[base]) Using cached kivy_deps.sdl2-0.7.0-cp310-cp310-win_amd64.whl.metadata (206 bytes) Collecting kivy-deps.glew~=0.3.1 (from kivy[base]) Using cached kivy_deps.glew-0.3.1-cp310-cp310-win_amd64.whl.metadata (232 bytes) Collecting pypiwin32 (from kivy[base]) Using cached pypiwin32-223-py3-none-any.whl.metadata (236 bytes) Collecting pillow<11,>=9.5.0 (from kivy[base]) Using cached pillow-10.4.0-cp310-cp310-win_amd64.whl.metadata (9.3 kB) Collecting requests (from kivy[base]) Using cached requests-2.32.3-py3-none-any.whl.metadata (4.6 kB) Requirement already satisfied: pywin32>=223 in f:\apps\source\repos\24.2\pbe_24.2_8001\.venv\lib\site-packages (from pypiwin32->kivy[base]) (306) Collecting charset-normalizer<4,>=2 (from requests->kivy[base]) Using cached charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl.metadata (34 kB) Collecting idna<4,>=2.5 (from requests->kivy[base]) Using cached idna-3.10-py3-none-any.whl.metadata (10 kB) Collecting urllib3<3,>=1.21.1 (from requests->kivy[base]) Using cached urllib3-2.2.3-py3-none-any.whl.metadata (6.5 kB) Collecting certifi>=2017.4.17 (from requests->kivy[base]) Using cached certifi-2024.8.30-py3-none-any.whl.metadata (2.2 kB) Using cached Kivy_examples-2.3.0-py2.py3-none-any.whl (10.1 MB) Using cached kivy_deps.angle-0.4.0-cp310-cp310-win_amd64.whl (5.1 MB) Using cached kivy_deps.glew-0.3.1-cp310-cp310-win_amd64.whl (123 kB) Using cached kivy_deps.sdl2-0.7.0-cp310-cp310-win_amd64.whl (3.5 MB) Using cached Kivy_Garden-0.1.5-py3-none-any.whl (4.6 kB) Using cached pillow-10.4.0-cp310-cp310-win_amd64.whl (2.6 MB) Using cached docutils-0.21.2-py3-none-any.whl (587 kB) Using cached Kivy-2.3.0-cp310-cp310-win_amd64.whl (4.6 MB) Using cached pypiwin32-223-py3-none-any.whl (1.7 kB) Using cached requests-2.32.3-py3-none-any.whl (64 kB) Using cached certifi-2024.8.30-py3-none-any.whl (167 kB) Using cached charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl (100 kB) Using cached idna-3.10-py3-none-any.whl (70 kB) Using cached urllib3-2.2.3-py3-none-any.whl (126 kB) Installing collected packages: kivy-deps.sdl2, kivy-deps.glew, kivy-deps.angle, urllib3, pypiwin32, pillow, idna, docutils, charset-normalizer, certifi, requests, Kivy-Garden, kivy_examples, kivy Successfully installed Kivy-Garden-0.1.5 certifi-2024.8.30 charset-normalizer-3.3.2 docutils-0.21.2 idna-3.10 kivy-2.3.0 kivy-deps.angle-0.4.0 kivy-deps.glew-0.3.1 kivy-deps.sdl2-0.7.0 kivy_examples-2.3.0 pillow-10.4.0 pypiwin32-223 requests-2.32.3 urllib3-2.2.3
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
class Retangulo:
def __init__(self, base, altura):
self.base = base
self.altura = altura
def calcular_area(self):
return self.base * self.altura
class CalculadoraAreaApp(App):
def build(self):
layout = BoxLayout(orientation='vertical')
self.base_input = TextInput(multiline=False)
self.altura_input = TextInput(multiline=False)
self.resultado_label = Label(text='Resultado:')
calcular_button = Button(text='Calcular')
calcular_button.bind(on_press=self.calcular_area)
layout.add_widget(self.base_input)
layout.add_widget(self.altura_input)
layout.add_widget(calcular_button)
layout.add_widget(self.resultado_label)
return layout
def calcular_area(self, instance):
try:
base = float(self.base_input.text)
altura = float(self.altura_input.text)
retangulo = Retangulo(base, altura)
resultado = retangulo.calcular_area()
self.resultado_label.text = f'A área é: {resultado}'
except ValueError:
self.resultado_label.text = 'Digite valores numéricos.'
if __name__ == '__main__':
CalculadoraAreaApp().run()
Vamos criar um exemplo mais complexo: Simulando um jogo de RPG simples¶
Contexto:
Imagine que queremos criar um jogo de RPG básico, onde o jogador possui um personagem com atributos como nome, classe, pontos de vida e mana. O personagem pode atacar, defender e usar habilidades.
### 1. Programação Procedural
def criar_personagem():
nome = input("Digite o nome do seu personagem: ")
classe = input("Digite a classe do seu personagem: ")
vida = 100
mana = 50
return nome, classe, vida, mana
def atacar(vida_inimigo):
dano = 10
vida_inimigo -= dano
print("Você causou 10 de dano ao inimigo!")
# Criar o personagem
nome, classe, vida, mana = criar_personagem()
# Combate
vida_inimigo = 100
while vida > 0 and vida_inimigo > 0:
acao = input("O que você deseja fazer? (atacar/defender): ")
if acao == "atacar":
atacar(vida_inimigo)
# ... outras ações
print("Fim do jogo!")
### 2. Programação Orientada a Objetos
class Personagem:
def __init__(self, nome, classe, vida=100, mana=50):
self.nome = nome
self.classe = classe
self.vida = vida
self.mana = mana
def atacar(self, inimigo):
dano = 10
inimigo.vida -= dano
print(f"{self.nome} causou 10 de dano a {inimigo.nome}!")
# Criar os personagens
jogador = Personagem("Herói", "Guerreiro")
inimigo = Personagem("Minotauro", "Bárbaro")
# Combate
while jogador.vida > 0 and inimigo.vida > 0:
acao = input("O que você deseja fazer? (atacar/defender): ")
if acao == "atacar":
jogador.atacar(inimigo)
# ... outras ações
print("Fim do jogo!")
Análise:
- Procedural: O código é dividido em funções que realizam tarefas específicas. Os dados do personagem são armazenados em variáveis globais ou passados como argumentos para as funções.
- Orientado a Objetos: O personagem é representado por uma classe
Personagem
, com atributos e métodos. As ações do personagem são realizadas através dos métodos da classe.
Vantagens da POO neste exemplo:
- Reutilização de código: A classe
Personagem
pode ser reutilizada para criar outros personagens com diferentes atributos. - Organização: O código fica mais organizado e fácil de entender, pois cada parte do personagem (atributos e ações) está encapsulada na classe.
- Extensibilidade: É mais fácil adicionar novas funcionalidades ao jogo, como novas classes de personagens ou habilidades.
Considerações:
- Complexidade: Para jogos mais complexos, a POO é essencial para gerenciar a quantidade de dados e interações entre os objetos.
- Aprendizado: A POO pode exigir um pouco mais de tempo para aprender, mas os benefícios a longo prazo são significativos.
Próximos passos:
- Expansão do jogo: Adicione mais classes, como itens, magias e inimigos.
- Interface gráfica: Utilize uma biblioteca como Pygame para criar uma interface gráfica para o jogo.
- Sistema de combate: Implemente um sistema de combate mais complexo, com diferentes tipos de ataques e defesas.
Possíveis tópicos:
- Herança: Crie classes derivadas de
Personagem
para representar diferentes tipos de personagens (mago, arqueiro, etc.). - Encapsulamento: Proteja os atributos da classe para evitar modificações indesejadas.
Lembre-se: A escolha entre programação procedural e orientada a objetos depende do contexto e da complexidade do problema. Em muitos casos, a combinação de ambos os paradigmas pode levar a soluções mais elegantes e eficientes.
class Personagem:
def __init__(self, nome, classe, vida=100, mana=50):
self.nome = nome
self.classe = classe
self.vida = vida
self.mana = mana
def atacar(self, inimigo):
dano = 10
inimigo.vida -= dano
print(f"{self.nome} causou {dano} de dano a {inimigo.nome}!")
class Mago(Personagem):
def __init__(self, nome):
super().__init__(nome, "Mago", mana=100)
def lançar_magia(self, inimigo):
if self.mana >= 20:
dano = 15
self.mana -= 20
inimigo.vida -= dano
print(f"{self.nome} lançou uma magia, causando {dano} de dano a {inimigo.nome}!")
else:
print(f"{self.nome} não tem mana suficiente para lançar uma magia.")
class Arqueiro(Personagem):
def __init__(self, nome):
super().__init__(nome, "Arqueiro")
def atirar(self, inimigo):
dano = 12
inimigo.vida -= dano
print(f"{self.nome} atirou uma flecha, causando {dano} de dano a {inimigo.nome}!")
# Como usar
mago = Mago("Gandalf")
arqueiro = Arqueiro("Legolas")
mago.lançar_magia(inimigo)
arqueiro.atirar(inimigo)
The Kernel crashed while executing code in the current cell or a previous cell. Please review the code in the cell(s) to identify a possible cause of the failure. Click <a href='https://aka.ms/vscodeJupyterKernelCrash'>here</a> for more info. View Jupyter <a href='command:jupyter.viewOutput'>log</a> for further details.
Protegendo os Atributos da Classe Personagem¶
A proteção dos atributos é fundamental para garantir a integridade dos dados e evitar comportamentos inesperados em nossos programas.
Como aplicar o encapsulamento na classe Personagem:
Vamos adaptar o código da classe Personagem para proteger os atributos vida
e mana
, permitindo apenas modificações através dos métodos set_vida
e set_mana
. Isso nos dará controle sobre como esses valores são alterados, evitando que sejam atribuídos valores inválidos ou inconsistentes.
class Personagem:
def __init__(self, nome, classe, vida=100, mana=50):
self._nome = nome
self._classe = classe
self._vida = vida
self._mana = mana
def get_nome(self):
return self._nome
def get_classe(self):
return self._classe
def get_vida(self):
return self._vida
def set_vida(self, nova_vida):
if nova_vida >= 0:
self._vida = nova_vida
else:
print("A vida não pode ser negativa.")
def get_mana(self):
return self._mana
def set_mana(self, nova_mana):
if nova_mana >= 0:
self._mana = nova_mana
else:
print("A mana não pode ser negativa.")
def atacar(self, inimigo):
dano = 10
inimigo.set_vida(inimigo.get_vida() - dano)
print(f"{self.get_nome()} causou {dano} de dano a {inimigo.get_nome()}!")
def receber_dano(self, dano):
nova_vida = self.get_vida() - dano
self.set_vida(nova_vida)
Analisando as mudanças:
- Atributos privados: Os atributos
_vida
e_mana
agora são precedidos por um underscore, indicando que são privados e devem ser acessados apenas através dos métodos getters e setters. - Métodos getters e setters: Foram adicionados métodos
get_vida
,set_vida
,get_mana
eset_mana
para controlar o acesso aos atributos. - Validação: Os métodos
set_vida
eset_mana
verificam se o novo valor é válido antes de atribuí-lo ao atributo. - Método receber_dano: Foi criado um método
receber_dano
para encapsular a lógica de redução da vida do personagem, utilizando os métodos getters e setters.
Vantagens do encapsulamento:
- Integridade dos dados: Evita que a vida ou a mana do personagem sejam definidas como valores negativos.
- Flexibilidade: Permite adicionar mais validações ou lógica aos métodos getters e setters no futuro, sem afetar o código que utiliza a classe.
- Manutenibilidade: Facilita a manutenção do código, pois as regras de negócio estão centralizadas nos métodos getters e setters.
- Segurança: Protege os dados internos da classe de modificações acidentais.
Exemplo de uso:
# Como usar
personagem = Personagem("Herói", "Guerreiro")
print(personagem.get_vida()) # Imprime: 100
# Tentativa de modificar a vida diretamente (não recomendado)
# personagem._vida = -10 # Isso causaria um erro
# Forma correta de modificar a vida
personagem.set_vida(80)
print(personagem.get_vida()) # Imprime: 80
100 80
Próximos passos:
- Mais validações: Adicione mais validações aos métodos getters e setters para garantir a consistência dos dados.
- Eventos: Implemente um sistema de eventos para notificar quando a vida ou a mana de um personagem atingem um determinado valor.
- Propriedades: Explore o uso de propriedades em Python para tornar o acesso aos atributos mais intuitivo.
Com o encapsulamento, você terá um código mais robusto, seguro e fácil de manter.