Pular para o conteúdo

Lista 02 - Classes e Objetos

Lista de exercícios sobre Orientações a Objetos em Python. Você foi contratado(a) como dev júnior da Byte Games, um estúdio de jogos mobile que está dando seus primeiros passos. A cada nível, uma nova missão dentro do estúdio: modelar classes, escrever construtores, criar métodos e proteger os dados dos seus sistemas. Resolva os exercícios em ordem, cada nível constrói a base para o próximo.


Nesta missão você vai trabalhar com a classe modelo dos sistemas do estúdio, criando objetos a partir dela.


Competência: Criação de objetos a partir de uma classe, acesso a atributos e chamada de métodos.

Contexto:
O estúdio prepara um arcade de corrida estilo kart. O time de gameplay já definiu a classe Kart e a colou no código abaixo. Seu papel é usar essa classe para cadastrar os três pilotos confirmados do jogo e colocá-los na pista.

Classe fornecida:

class Kart:
def __init__(self, piloto, cor):
self.piloto = piloto
self.cor = cor
def acelerar(self):
print(f"{self.piloto} acelerou o kart {self.cor}!")
def derrapar(self):
print(f"{self.piloto} derrapou na curva!")

Instruções:

  • Crie três objetos da classe Kart, um para cada piloto, com os dados abaixo:
    • Mario, kart vermelho;
    • Luigi, kart verde;
    • Peach, kart rosa.
  • Imprima o nome do piloto e a cor de cada kart, um por linha.
  • Chame o método acelerar() no kart do Mario e o método derrapar() no kart da Peach.

Saída esperada:

Mario pilota o kart vermelho.
Luigi pilota o kart verde.
Peach pilota o kart rosa.
Mario acelerou o kart vermelho!
Peach derrapou na curva!

Exercício 1.2: Recrutando o Inimigo (Múltipla escolha)

Seção intitulada “Exercício 1.2: Recrutando o Inimigo (Múltipla escolha)”

Competência: Sintaxe correta para instanciar objetos.

Contexto:
No novo jogo de terror do estúdio, a classe Inimigo já está pronta:

class Inimigo:
def __init__(self, nome, vida):
self.nome = nome
self.vida = vida
def atacar(self):
print(f"{self.nome} ataca você!")

Você precisa criar um zumbi com 100 de vida. Qual das opções cria corretamente esse objeto?

Alternativas:

  • (A) inimigo = Inimigo("Zumbi", 100)
  • (B) inimigo = Inimigo(nome="Zumbi")
  • (C) zumbi = nome.Inimigo("Zumbi", 100)
  • (D) inimigo = new Inimigo("Zumbi", 100)

Competência: Distinguir classe de objeto e aplicar a analogia do molde.

Pergunta:
Explique, com suas palavras, a diferença entre classe e objeto usando a analogia do molde de kart. Depois, dê um exemplo da engenharia da computação (fora dos jogos) em que a mesma ideia de “molde e peça produzida” se aplica.


O estúdio precisa que você escreva construtores capazes de inicializar os atributos de cada novo objeto. O __init__ é o seu instrumento.


Exercício 2.1: Criando Personagens (Programação)

Seção intitulada “Exercício 2.1: Criando Personagens (Programação)”

Competência: Escrita de construtores com __init__ e criação de objetos com atributos iniciais.

Contexto:
O RPG do estúdio está em pré-produção. Todo herói precisa nascer com nome, vida e mana. Você é responsável por finalizar o esqueleto da classe Personagem, escrevendo o construtor que recebe esses três valores e os guarda como atributos.

Instruções:

  • Defina a classe Personagem e implemente o construtor que armazena os valores recebidos nos atributos nome, vida e mana.
  • Crie os personagens Aric (vida 100, mana 50) e Luna (vida 80, mana 100).
  • Imprima as informações de cada personagem no formato abaixo.

Saída esperada:

Aric tem 100 de vida e 50 de mana.
Luna tem 80 de vida e 100 de mana.

Exercício 2.2: Montando a Playlist (Múltipla escolha)

Seção intitulada “Exercício 2.2: Montando a Playlist (Múltipla escolha)”

Competência: Reconhecer o construtor compatível com uma chamada de criação.

Contexto:
O módulo de trilha sonora do estúdio instancia uma playlist assim:

playlist = Playlist("Rock", 15)

Qual das opções implementa corretamente a classe Playlist para que essa chamada funcione como esperado (gênero "Rock", quantidade 15)?

Alternativas:

  • (A)
class Playlist:
def __init__(self, genero, quantidade):
self.genero = genero
self.quantidade = quantidade
  • (B)
class Playlist:
def __init__(self):
self.genero = "Rock"
self.quantidade = 15
  • (C)
class Playlist:
def __init__(genero, quantidade):
self.genero = genero
self.quantidade = quantidade
  • (D)
class Playlist:
def __init__(self, quantidade, genero):
self.genero = genero
self.quantidade = quantidade

Competência: Compreender o papel e o momento de execução do construtor.

Pergunta:
Explique o que é o método __init__ e em que momento exato ele é executado. O que acontece com os atributos de um objeto se uma classe não definir esse método? Justifique por que é importante que a inicialização dos atributos aconteça no construtor em vez de ficar solta no código.


Agora você vai dar comportamento aos objetos. Nesta missão, self é a chave para cada objeto manipular seus próprios dados.


Competência: Criação de métodos que usam self para ler e alterar atributos do próprio objeto.

Contexto:
O FPS da Byte está com o combate quase pronto. A classe Jogador já tem o construtor definido. Seu trabalho é implementar os três métodos de combate que manipulam os atributos de cada jogador de forma individual.

Classe fornecida:

class Jogador:
def __init__(self, nome, vida, dano):
self.nome = nome
self.vida = vida
self.dano = dano

Instruções:

  • Implemente o método receber_dano(quantidade), que reduz a vida do próprio jogador.
  • Implemente o método atacar(inimigo), que reduz a vida do inimigo usando o próprio dano do atacante e imprime a mensagem {nome} causou {dano} de dano em {nome_do_inimigo}!.
  • Implemente o método curar(quantidade), que aumenta a vida do próprio jogador e imprime {nome} recuperou {quantidade} de vida!.
  • No programa principal, execute exatamente a sequência abaixo:
player = Jogador("Rafa", 100, 25)
chefe = Jogador("Hydra", 200, 40)
player.atacar(chefe)
chefe.atacar(player)
player.curar(15)
print(player.vida)
print(chefe.vida)

Saída esperada:

Rafa causou 25 de dano em Hydra!
Hydra causou 40 de dano em Rafa!
Rafa recuperou 15 de vida!
75
175

Exercício 3.2: Dois Contadores (Múltipla escolha)

Seção intitulada “Exercício 3.2: Dois Contadores (Múltipla escolha)”

Competência: Prever o comportamento de um método utilizando self em objetos diferentes.

Contexto:
O estúdio usa essa classe para contar eventos no jogo:

class Contador:
def __init__(self, valor):
self.valor = valor
def incrementar(self):
self.valor += 1
contador_a = Contador(5)
contador_b = Contador(10)
contador_a.incrementar()
contador_b.incrementar()
contador_b.incrementar()
print(contador_a.valor, contador_b.valor)

Qual será a saída impressa?

Alternativas:

  • (A) 6 12
  • (B) 8 12
  • (C) 6 11
  • (D) 5 10

Competência: Explicar o papel de self em métodos de instância.

Pergunta:
O que é self em um método de classe? Por que ele aparece como primeiro parâmetro de todo método de instância? Descreva o erro que ocorre quando você chama um método esquecendo o self na definição, e explique por que self é essencial quando dois objetos da mesma classe precisam se relacionar (como um herói atacando outro).


A Byte ganhou um módulo financeiro. Dinheiro não pode ser alterado por qualquer um, seus dados precisam de proteção. Bem-vindo ao encapsulamento.


Competência: Atributos privados, getters e setters com validação.

Contexto:
O jogo vai ter moeda digital própria. Você precisa blindar a classe CarteiraDigital para que ninguém mexa no saldo diretamente. A classe já possui os atributos __titular e __saldo no construtor, mas todas as portas de acesso ainda precisam ser construídas.

Classe fornecida:

class CarteiraDigital:
def __init__(self, titular, saldo_inicial):
self.__titular = titular
self.__saldo = saldo_inicial

Instruções:

  • Crie o getter get_titular(), que retorna o nome do titular.
  • Crie o getter get_saldo(), que retorna o saldo atual.
  • Crie o setter set_saldo(valor), que recusa valores negativos: se valor for menor que zero, imprima Erro: saldo não pode ser negativo. sem alterar o saldo; caso contrário, atualize o saldo.
  • No programa principal, execute exatamente a sequência abaixo:
carteira = CarteiraDigital("Ana", 100.0)
print(f"Saldo de {carteira.get_titular()}: R$ {carteira.get_saldo()}")
carteira.set_saldo(carteira.get_saldo() + 250.0)
print(f"Saldo de {carteira.get_titular()}: R$ {carteira.get_saldo()}")
carteira.set_saldo(-50.0)
print(f"Saldo de {carteira.get_titular()}: R$ {carteira.get_saldo()}")

Saída esperada:

Saldo de Ana: R$ 100.0
Saldo de Ana: R$ 350.0
Erro: saldo não pode ser negativo.
Saldo de Ana: R$ 350.0

Exercício 4.2: Proibido Tocar (Múltipla escolha)

Seção intitulada “Exercício 4.2: Proibido Tocar (Múltipla escolha)”

Competência: Identificar o acesso correto e o acesso bloqueado a atributos privados.

Contexto:
O estúdio rastreia o carrinho de compras dos jogadores com esta classe:

class Carrinho:
def __init__(self):
self.__itens = 0
def get_itens(self):
return self.__itens
carrinho = Carrinho()

Qual das linhas abaixo, executada logo após a criação do carrinho, gera um erro AttributeError?

Alternativas:

  • (A) print(carrinho.get_itens())
  • (B) print(carrinho.__itens)
  • (C) print(carrinho.get_itens() + 1)
  • (D) print(carrinho.get_itens)

Competência: Explicar o encapsulamento e o roteiro para aplicá-lo.

Pergunta:
Explique o que é encapsulamento e os três passos do roteiro para aplicá-lo em uma classe (atributos privados, getters e setters). Por que não basta apenas tornar os atributos privados, por que os setters, em especial, são úteis quando o dado precisa de regras (como um saldo que não pode ser negativo)? Dê um exemplo de um problema que pode acontecer se todos os atributos de um sistema forem públicos.


Missão final: o MOBA da Byte está em beta e você vai montar o coração do combate. Este desafio integra tudo que você aprendeu: criação de classe, construtor, self, encapsulamento e a interação entre objetos.


Exercício 5.1: Construindo o Heroi (Programação)

Seção intitulada “Exercício 5.1: Construindo o Heroi (Programação)”

Competência: Projetar uma classe completa integrando construtor, métodos com self e encapsulamento com getters/setters.

Contexto:
Cada partida do MOBA é um duelo entre dois heróis. Você deve implementar a classe Heroi do zero, com todos os atributos blindados, e simular um duelo entre dois heróis até um deles ser derrotado.

Instruções:

  • Crie a classe Heroi com o construtor com os atributos nome, vida e ataque.
  • Torne os atributos privados.
  • Crie getters e setters para os três atributos. Os setters de vida e ataque devem ajustar valores fora do intervalo, limitando vida entre 0 e 100 e ataque entre 0 e 99.
  • Implemente o método atacar(oponente), que reduz a vida do oponente de acordo com o ataque do herói (use os getters/setters do oponente) e imprime {nome} ataca {nome_do_oponente} ({nome_do_oponente}: {vida_atual}).
  • Implemente o método esta_vivo(), que retorna True se a vida for maior que 0.
  • No programa principal, declare o vencedor da partida com o laço abaixo:
alfa = Heroi("Alfa", 100, 35)
beta = Heroi("Beta", 90, 20)
while alfa.esta_vivo() and beta.esta_vivo():
alfa.atacar(beta)
if beta.esta_vivo():
beta.atacar(alfa)
if alfa.esta_vivo():
print("Alfa venceu o duelo!")
else:
print("Beta venceu o duelo!")

Saída esperada:

Alfa ataca Beta (Beta: 55)
Beta ataca Alfa (Alfa: 80)
Alfa ataca Beta (Beta: 20)
Beta ataca Alfa (Alfa: 60)
Alfa ataca Beta (Beta: 0)
Alfa venceu o duelo!

Competência: Interpretar o estado dos objetos após métodos que usam self e getters/setters.

Contexto:
Em outra partida de teste, o código a seguir foi executado com a classe Heroi do exercício anterior:

x = Heroi("X", 80, 30)
y = Heroi("Y", 70, 25)
x.atacar(y)
y.atacar(x)
x.atacar(y)

Qual é o estado correto dos heróis ao final (vida de x e vida de y)?

Alternativas:

  • (A) x: 55, y: 10
  • (B) x: 50, y: 20
  • (C) x: 55, y: 30
  • (D) x: 50, y: 10

Competência: Avaliar decisões de projeto em orientação a objetos.

Pergunta:
Sobre a solução do duelo de heróis, responda com argumentos:

  1. Por que o atributo __vida de um herói deve ser privado e alterado apenas por métodos como atacar e pelos setters?
  2. O método atacar(oponente) poderia ser uma função solta do tipo atacar(alfa, beta) do lado de fora da classe? Compare as duas formas em termos de organização e reutilização de código.
  3. O que self representa dentro do método atacar quando o duelo é executado? Explique o que acontece quando o programa cria alfa e beta, mostrando que cada objeto mantém seu próprio __vida e __ataque.