Activité pratique 1

Durée2h30

Consignes globales

L’objectif de cette séance est de vous faire pratiquer l’identification des classes que peuvent constituer un logiciel objet qui répond à un problème donné en utilisant quelques exercices simples.

Important

Pour cette séance vous n’allez pas écrire du code, vous allez réfléchir à comment structurer une solution à un problème donné et vous donnerez le pseudo-code correspondant. Vous n’avez donc pas besoin d’utiliser votre ordinateur, un simple papier/crayon suffit.

Contenu de l’activité

Pour chaque exercice vous devez identifier les objets, les classes, les méthodes, les attributs, les associations et les relations d’héritage (voir tableau 1). Pour un objet, donnez sa classe ; pour une classe, donnez ses attributs et méthodes ; pour une méthode, donnez ses arguments et, si besoin, son résultat ; pour un attribut, donnez son type et pour une association précisez les classes qu’elle met en relation.

Tableau 1. Classes, objets, attributs, méthodes et associations.

Mage, guerrier, voleur

L’objectif de cet exercice est de documenter, sous la forme de tableau les différents éléments qui rentrent en jeu dans une version objet du jeu mage, guerrier, voleur qui a été introduit pendant le cours.

Pour rappel, les règles du jeu sont les suivantes : on souhaite programmer, en pseudo-code, un jeu de rôle de combat. Dans le jeu on a trois types de personnages : le mage, le guerrier et le voleur. Ils ont tous des points de vie au départ (hp), causent des dégâts à ses opposants (damage) et ont un personnage qui leur cause 2 fois plus de dégâts (weakness). Plus précisément :

  • Mage : hp = 50, damage = 10, weakness = Guerrier
  • Guerrier : hp = 40, damage = 12, weakness = Voleur
  • Voleur : hp = 45, damage = 8, weakness = Mage

De plus, à chaque victoire, un personnage gagne en expérience et ces gains sont différents selon le personnage :

  • Mage : +5 hp ; +1 damage
  • Guerrier : +10 hp; +2 damage
  • Voleur : +8 hp; +1 damage

Une partie se déroule de la manière suivante : à chaque tour, l’attaquant et la cible sont choisis de manière aléatoire. Après l’attaque, les dégâts infligés à la cible sont calculés : la puissance d’attaque de l’attaquant ou 2 fois cette puissance si l’attaquant est le point faible de la cible. La partie est finie quand il ne reste qu’un seul survivant. Enfin, le mage n’est vaincu que lorsque ses points de vie sont <= -5

Jeu sans héritage

À faire

Complétez le tableau pour la version du jeu sans héritage.

Correction
Cliquez ici pour afficher la correction.

Le point à ne pas manquer est d’indiquer le type des attributs comme des méthodes. C’est lui qui dit quelles opérations sont possibles :

  • le type des paramètres d’une méthode. Ex. dans la méthode attack() on met le type du paramètre et pas son nom. C’est cette information qui est importante pour pouvoir ensuite donner le code (ou pseudo-code) de la méthode ;
  • le type du résultat rendu par la méthode. Ex. dans la méthode is_alive() on dit que c’est un booléen qui est rendu par la méthode ;
  • le type des attributs, qui donne des indications du type d’opérations qu’il sera possible de réaliser sur l’attribut. Par ex. il sera possible d’ajouter des points de vie à un personnage mais il n’est pas possible de faire ce type d’opérations sur son nom.
Tableau 2. Mage-Guerrier-Voleur sans héritage.

Jeu avec héritage

À faire

Complétez le tableau pour la version du jeu avec héritage.

Correction
Cliquez ici pour afficher la correction.

Deux versions sont données ici, chacune avec ses avantages et ses inconvénients. Dans la première, rien ne distingue un mage, un guerrier et un voleur : il n’y a que des personnages. Dans la seconde, chaque type a sa classe, mais on ne pourra pas en ajouter si les règles du jeu changent : c’est moins flexible. Le choix entre les deux dépend du dialogue avec le client et de ce qu’il veut, tel qu’il ressort de l’énoncé du problème.

Tableau 3a. Mage-Guerrier-Voleur avec héritage.
Tableau 3b. Mage-Guerrier-Voleur avec héritage.

L’ascenseur

On souhaite modéliser le fonctionnement (simplifié) d’un ascenseur. L’ascenseur ferme ses portes avant de se déplacer vers un autre étage. De plus, l’ascenseur connaît tous les déplacements d’un étage à un autre effectués depuis sa mise en service.

Identification des éléments

À faire

Complétez le tableau avec les éléments qui permettent de répondre à l’énoncé.

Correction
Cliquez ici pour afficher la correction.

Deux solutions sont proposées ici. La différence dans les deux solutions proposées est l’identification de la classe Floor. La question de la garder se pose puisqu’elle se résume à un entier et aucune méthode n’y est attachée. Si un étage (tel que vu par le système d’ascenseur) ne présente aucune autre caractéristique que celles associées à son numéro d’étage, vous n’aurez peut-être pas besoin d’une classe Floor distincte. Si, toutefois, les étages ont des propriétés autres que celles de leurs numéros, alors une classe Floor sera appropriée. Par exemple, certains étages peuvent avoir des droits d’accès spéciaux définissant qui peut les visiter ; dans ce cas, la classe Floor pourrait inclure une caractéristique telle que rights:[Authorization]. Ou alors si le système est conçu pour un cabinet d’architectes, la classe Floor pourrait avoir d’autres caractéristiques comme la surface…

Et quel est l’intérêt de la classe Move ? Un déplacement peut être représenté par un dictionnaire et l’historique serait alors une liste de dictionnaires. Comme le problème est simple, effectivement c’est envisageable et dans ce cas on rentre dans le cas de la programmation procédurale (au détail près de la classe Elevator). Mais du point de vue du problème posé et pour aboutir à une solution OO, la notion de déplacement est une élément fondamental du système d’ascenseur et donc, elle est réifiée par cette classe.

Tableau 4a. Déplacement d’un ascenseur avec classe Floor.
Tableau 4b. Déplacement d’un ascenseur avec sans classe Floor.

Pseudo-code

À faire

Donnez le pseudo code qui correspond à la création d’un ascenseur, la fermeture de ses portes et son déplacement au 4ème étage.

Correction
Cliquez ici pour afficher la correction.
# With Floor class
rdc = Floor(0, "rdc")
etage4 = Floor (4, "etage4")
ascenseur_bibliotheque = Elevator(rdc)
ascenseur_bibliotheque.close_doors()
ascenseur_bibliotheque.go_to(etage4)

# Without Floor class
asascenseur_bibliotheque = Elevator(0)
ascenseur_bibliotheque.close_doors()
ascenseur_bibliotheque.go_to(4)

Un système de gestion de comptes bancaires

On souhaite créer un système très simple de gestion de comptes bancaires. Le système (logiciel) doit permettre de gérer les comptes bancaires des clients (en ajouter, supprimer, modifier les informations associées) et les transactions (créditer/débiter un compte et connaître l’historique des transactions faites sur un compte) qui y sont faites pour une institution bancaire.

Identification des éléments

À faire

Complétez le tableau avec les éléments qui permettent de répondre à l’énoncé.

Correction
Cliquez ici pour afficher la correction.
Tableau 5. Logiciel de gestion de comptes bancaires.

Pseudo-code instanciation

À faire

Donnez le pseudo-code qui crée la banque cmb, ouvre un compte pour Louis Dupond et crédite 100 euros sur ce compte.

Correction
Cliquez ici pour afficher la correction.
# Create the bank
cmb = Bank("Credit Mutuel")
# Create the client
client1 = Client("Dupond", "Louis")
# Open a new account in the bank for the client
compte1 = cmb.open_account(client1)
# Credit some euros into the new account
cmb.credit(compte1, 100)

Pseudo-code constructeur

À faire

Donnez le pseudo-code du constructeur de la classe Bank et de la méthode open_account.

Correction
Cliquez ici pour afficher la correction.
CLASS Bank() :
	# Attributes of the class
	PRIVATE ATTRIBUTE name: str
	PRIVATE ATTRIBUTE accounts: [Account]

	# The constructor initializes attributs
	CONSTRUCT Bank(name: str) :
		self.name = name
		self.accounts = []

	# Open an account = create the new account and
	# add it to the list of accounts of the bank
	METHOD open_account(client: Client) -> Account:
		account = Account(client)
		self.accounts.append(account)
		return account

Pseudo-code classe BankAccount

À faire

Donnez le pseudo-code de la classe BankAccount.

Correction
Cliquez ici pour afficher la correction.
CLASS Account:
	# Attributes of the class
	PRIVATE ATTRIBUTE client: Client
	PRIVATE ATTRIBUTE balance: float
	PRIVATE ATTRIBUTE transactions: [Transaction]

	# The constructor initializes attributs
	CONSTRUCT Account(client: Client) :
		self.client = client
		self.balance = 0
		self.transactions = []

	# Compute the new balance and
	# add the transaction to the list of transactions of the account
	METHOD credit(amount: float) -> float:
		self.balance += amount
		tx1 = Transaction(now, montant, "CREDIT")
		self.transactions.append(tx1)
		return self.balance

	# Compute the new balance and
	# add the transaction to the list of transactions of the account
	METHOD debit(amount: float) -> float:
		if self.balance - amount > 0 :
			self.balance -= amount
			tx1 = Transaction(now, amount, "DEBIT")
			self.transactions.append(tx1)
		return self.balance

Version avec contrôle du montant passé en paramètre.

CLASS Account:
	# Attributes of the class
	PRIVATE ATTRIBUTE client: Client
	PRIVATE ATTRIBUTE balance: float
	PRIVATE ATTRIBUTE transactions: [Transaction]

	# The constructor initializes attributs
	CONSTRUCT Account(client: Client) :
		self.client = client
		self.balance = 0
		self.transactions = []

	# Compute the new balance and
	# add the transaction to the list of transactions of the account
	METHOD credit(amount: float) -> float:
		if amount > 0:
			self.balance += montant
			tx1 = Transaction(now, montant, "CREDIT")
			self.transactions.append(tx1)
		return self.balance


	# Compute the new balance and
	# add the transaction to the list of transactions of the account
	METHOD debit(amount: float) -> float :
		if amount > 0:
			if self.balance - amount > 0 :
				self.balance -= amount
				tx1 = Transaction(now, amount, "DEBIT")
				self.transactions.append(tx1)
		return self.balance

Nouveaux types de comptes

À faire

On souhaite maintenant qu’une banque puisse gérer des comptes jeunes, qui, au démarrage ont un solde de 500 euros et pour lesquels il n’est plus possible de débiter de l’argent 10 ans après leur ouverture. Modifiez le tableau construit dans les questions précédentes pour tenir compte de cette contrainte.

Correction
Cliquez ici pour afficher la correction.
Tableau 5. Logiciel de gestion de comptes bancaires avec comptes jeune.

Pseudo-code gestion des types de comptes

À faire

Donnez le pseudo-code des modifications apportées.

Correction
Cliquez ici pour afficher la correction.
CLASS YoungAccount(Account) :
	PRIVATE ATTRIBUTE opened: Date

	CONSTRUCT YoungAccount(client: Client) :
		self.client = client
		self.balance = 500
		self.opened = now
		self.transactions = []

	METHOD debit(amount: float) -> float:
		if now < self.opened + 10 :
			if self.balance - amount > 0 :
				self.balance -= amount
				tx1 = Transaction(now, amount, "DEBIT")
				self.transactions.append(tx1)
		return self.balance


CLASS Bank() :

	METHOD open_young_account(client: Client) --> YoungAccount :
		account = YoungAccount(client)
		self.accounts.append(account)
		return account

Pour aller plus loin

Parcours de soins dans un hôpital

On veut développer un logiciel pour informatiser le parcours de soins dans un hôpital. Dans le logiciel, on doit avoir le nom, prénom, date de naissance, sexe et actes médicaux que les patients ont subis. Ces actes peuvent être des diagnostics ou des soins. Les deux sont réalisés par un personnel soignant à une certaine date. Mais un soin produit une certaine amélioration de l’état du patient (en pourcentage) et un diagnostic a une certaine validité (fiabilité du diagnostic en pourcentage).

Identification des éléments

À faire

Complétez le tableau avec les éléments qui permettent de répondre à l’énoncé.

Correction
Cliquez ici pour afficher la correction.
Tableau 5. Logiciel de gestion des actes médicales d’un hôpital.

Pseudo-code instanciation

À faire

Donnez le pseudo-code qui crée l’hôpital Cavale Blanche, crée le dossier médical de Louis Dupond né à Valence et qui a été diagnostiqué le 08/07/2025 comme ayant (à 90% de chances) un Zona.

Correction
Cliquez ici pour afficher la correction.
# Create the hospital
cavale = Hospital("Cavale Blanche")
# Create the patient
patient1 = cavale.new_patient("Dupond", "Louis", Date("2002-12-10"), "Male")
# Create the medical act
medical_act = Diagnose(Date("2025/07/08"), "Mme Sabre", "Zona", 90)
cavale.new_medical_act(patient1, medical_act)

Pseudo-code classes

À faire

Donnez le pseudo-code des différentes classes identifiées.

Correction
Cliquez ici pour afficher la correction.
CLASS Hospital() :
	ATTRIBUTE name: str
	ATTRIBUTE patients: [Patient]

	CONSTRUCT (name: str) :
		self.name = name
		self.patients = []

	METHOD new_patient (lastname: str, firstname: str, birthday: Date, gender: str) :
		patient = Patient(lastname, firstname, birthday, gender)
		self.patients.append(patient)
		return patient

	METHOD new_medical_act(patient: Patient, act: MedicalAct):
		# Search the patient in the patients list
		if not patient in self.patients:
			return None
		the_patient = self.patients.search(patient)

		the_patient.new_medical_act(act)

	METHOD consult_patient(patient: Patient):
		# Search the patient in the patients list
		if not patient in self.patients:
			return None
		the_patient = self.patients.search(patient)
		return the_patient.get_medical_history()

CLASS Patient:
	lastname: str
	firstname: str
	birthday: Date
	gender: str
	medical_history:[MedicalAct]

	CONSTRUCT (lastname: str, firstname: str, birthday: Date, gender: str):
		self.lastname = lastname
		self.firstname = firstname
		self.birthday = birthday
		self.gender = gender
		self.medical_history = []

	METHOD new_medical_act(act: MedicalAct):
		self.medical_history.append(act)

	METHOD get_medical_history():
		return medical_history

CLASS MedicalAct :
	date: Date
	caregiver: str

	METHOD constructor(date: Date, caregiver: str) :
		self.date = date
		self.caregiver = caregiver

CLASS Diagnose(MedicalAct) :
	disease: str
	validity: int

	CONSTRUCT (date: Date, caregiver: str, disease: str, validity: int) :
		super(date, caregiver)
		self.disease = disease
		self.validity = validity

class Care(MedicalAct) :
	improvement: int

	CONSTRUCT (date: Date, caregiver: str, improvement: int) :
		super(date, caregiver)
		self.disease = disease
		self.improvement = improvement