Dans cet exposé, vous allez élaborer une application de révision des tables de multiplication, en tirant profit de la puissance de la programmation orientée objet (POO) avec Python.
Vous allez pratiquer les principes clés de la POO et leur application dans une solution entièrement opérationnelle.
Python est un langage de programmation multi-paradigme, ce qui signifie qu’en tant que développeurs, nous pouvons choisir l’approche la plus appropriée pour chaque situation et défi. La programmation orientée objet est l’un des paradigmes les plus utilisés pour la conception d’applications évolutives ces dernières décennies.
Les Fondamentaux de la POO
Nous allons examiner brièvement le concept essentiel de la POO en Python : les classes.
Une classe est un schéma qui définit la structure et le comportement des objets. Ce modèle nous permet de créer des instances, qui sont simplement des objets uniques résultant de la composition de la classe.
Une classe simple de livre, avec les attributs de titre et de couleur, pourrait être définie comme suit.
class Livre: def __init__(self, titre, couleur): self.titre = titre self.couleur = couleur
Pour générer des instances de la classe livre, nous devons faire appel à la classe en lui passant des arguments.
# Création d'objets instances de la classe Livre livre_bleu = Livre("L'enfant bleu", "Bleu") livre_vert = Livre("L'histoire de la grenouille", "Vert")
Une représentation graphique de notre programme actuel serait la suivante :
Il est remarquable que lorsque nous vérifions le type des instances livre_bleu et livre_vert, nous obtenons « Livre ».
# Affichage du type des livres print(type(livre_bleu)) # <class '__main__.Livre'> print(type(livre_vert)) # <class '__main__.Livre'>
Une fois ces concepts assimilés, nous pouvons amorcer la construction du projet 😃.
Énoncé du Projet
Dans le métier de développeur, la majorité du temps n’est pas consacrée à l’écriture de code. Selon The New Stack, nous ne consacrons qu’un tiers de notre temps à l’écriture ou à la refactorisation du code.
Les deux autres tiers sont dédiés à la lecture du code d’autrui et à l’analyse du problème sur lequel nous travaillons.
Pour ce projet, je vais donc formuler un énoncé de problème et nous allons analyser comment structurer notre application à partir de celui-ci. De cette façon, nous suivons le processus complet, de la réflexion sur la solution à sa mise en œuvre à travers le code.
Un enseignant du primaire souhaite un jeu pour évaluer les compétences en multiplication des élèves âgés de 8 à 10 ans.
Le jeu doit inclure un système de vies et de points. L’élève débute avec 3 vies et doit atteindre un certain nombre de points pour gagner. Un message « perdu » doit s’afficher si l’élève épuise toutes ses vies.
Le jeu doit proposer deux modes : multiplications aléatoires et révision de tables.
Le premier mode doit présenter à l’élève une multiplication aléatoire de 1 à 10, et il doit répondre correctement pour gagner un point. En cas d’erreur, l’élève perd une vie et le jeu se poursuit. L’élève ne gagne que lorsqu’il atteint 5 points.
Le deuxième mode doit présenter une table de multiplication de 1 à 10, où l’élève doit saisir le résultat de chaque multiplication. Après 3 échecs, l’élève perd. S’il termine correctement deux tables, la partie se termine.
Les exigences peuvent paraître un peu étoffées, mais nous allons les résoudre dans cet article 😁.
Diviser pour Mieux Régner
La compétence la plus importante en programmation est la résolution de problèmes. Il est essentiel d’avoir une stratégie avant de se lancer dans la programmation.
Il est toujours conseillé de scinder un problème complexe en sous-problèmes plus faciles à gérer et à résoudre de manière efficace.
Ainsi, pour la création d’un jeu, il est préférable de commencer par le décomposer en ses parties essentielles. Ces sous-problèmes seront bien plus faciles à résoudre.
Vous aurez alors une vision claire de la manière d’exécuter et d’intégrer le tout à l’aide de code.
Illustrons par un schéma ce à quoi pourrait ressembler le jeu.
Ce schéma illustre les relations entre les éléments de notre application. Les deux éléments principaux sont la multiplication aléatoire et la révision de tables de multiplication. Leur seul point commun est les attributs Points et Vies.
Forts de ces informations, passons au code.
Création de la Classe Mère du Jeu
Lorsque nous travaillons avec la programmation orientée objet, nous privilégions l’approche la plus propre pour éviter les répétitions de code. C’est ce que l’on appelle le principe DRY (ne vous répétez pas).
Note : Cet objectif ne consiste pas à écrire le moins de lignes de code possible (la qualité du code ne doit pas se mesurer à cet aspect), mais à abstraire la logique la plus fréquemment utilisée.
Conformément à cette idée, la classe mère de notre application doit établir la structure et le comportement souhaité des deux autres classes.
Voici comment cela peut être réalisé.
class JeuDeBase: # Longueur pour centrer les messages longueur_message = 60 description = "" def __init__(self, points_pour_gagner, nb_vies=3): """Classe de jeu de base Args: points_pour_gagner (int): points nécessaires pour terminer le jeu nb_vies (int): nombre de vies de l'élève. Par défaut, 3. """ self.points_pour_gagner = points_pour_gagner self.points = 0 self.vies = nb_vies def obtenir_entree_numerique(self, message=""): while True: # Obtient l'entrée de l'utilisateur entree_utilisateur = input(message) # Si l'entrée est numérique, la retourne # Sinon, affiche un message et recommence if entree_utilisateur.isnumeric(): return int(entree_utilisateur) else: print("L'entrée doit être un nombre") continue def afficher_message_bienvenue(self): print("JEU DE MULTIPLICATION PYTHON".center(self.longueur_message)) def afficher_message_perdu(self): print("DÉSOLÉ, VOUS AVEZ PERDU TOUTES VOS VIES".center(self.longueur_message)) def afficher_message_gagne(self): print(f"FÉLICITATIONS, VOUS AVEZ ATTEINT {self.points}".center(self.longueur_message)) def afficher_vies_actuelles(self): print(f"Actuellement, il vous reste {self.vies} vies\n") def afficher_score_actuel(self): print(f"\nVotre score est de {self.points}") def afficher_description(self): print("\n\n" + self.description.center(self.longueur_message) + "\n") # Méthode de lancement de base def lancer(self): self.afficher_message_bienvenue() self.afficher_description()
Cette classe semble assez imposante. Laissez-moi l’expliquer en détail.
Commençons par les attributs de classe et le constructeur.
Fondamentalement, les attributs de classe sont des variables créées à l’intérieur de la classe, mais en dehors du constructeur ou de toute méthode.
Les attributs d’instance, quant à eux, sont des variables créées uniquement à l’intérieur du constructeur.
La principale différence entre les deux réside dans leur portée. Les attributs de classe sont accessibles à la fois depuis un objet instance et depuis la classe. Les attributs d’instance, par contre, ne sont accessibles que depuis un objet instance.
jeu = JeuDeBase(5) # Accès à l'attribut de classe longueur_message de l'objet instance print(jeu.longueur_message) # 60 # Accès à l'attribut de classe longueur_message de la classe print(JeuDeBase.longueur_message) # 60 # Accès à l'attribut d'instance points de l'objet instance print(jeu.points) # 0 # Accès à l'attribut d'instance points de la classe print(JeuDeBase.points) # Erreur d'attribut
Un autre article pourrait approfondir ce sujet. Restez à l’écoute pour le lire.
La fonction obtenir_entree_numerique est utilisée pour contraindre l’utilisateur à saisir une entrée numérique. Cette méthode est conçue pour solliciter une entrée de l’utilisateur jusqu’à ce qu’elle soit numérique. Nous l’utiliserons ultérieurement dans les classes enfants.
Les méthodes d’affichage nous permettent d’éviter la répétition de l’affichage de la même chose à chaque événement dans le jeu.
Enfin, la méthode lancer n’est qu’un wrapper que les classes de multiplication aléatoire et de révision de tables utiliseront pour interagir avec l’utilisateur et rendre le tout fonctionnel.
Création des Classes Enfants
Après avoir créé la classe mère, qui établit la structure et une partie des fonctionnalités de notre application, il est temps de créer les classes de mode de jeu proprement dites, en utilisant la puissance de l’héritage.
Classe de Multiplication Aléatoire
Cette classe mettra en œuvre le « premier mode » de notre jeu. Elle utilisera naturellement le module random, qui nous donnera la possibilité de soumettre à l’utilisateur des opérations aléatoires de 1 à 10. Voici un article très intéressant sur le module random (et d’autres modules importants) 😉.
import random # Module pour les opérations aléatoires
class MultiplicationAleatoire(JeuDeBase): description = "Dans ce jeu, vous devez répondre correctement à la multiplication aléatoire.nVous gagnez si vous atteignez 5 points, ou vous perdez si vous épuisez toutes vos vies" def __init__(self): # Le nombre de points nécessaires pour gagner est de 5 # Transmet l'argument 5 "points_pour_gagner" super().__init__(5) def obtenir_nombres_aleatoires(self): premier_nombre = random.randint(1, 10) second_nombre = random.randint(1, 10) return premier_nombre, second_nombre def lancer(self): # Appel de la classe supérieure pour afficher les messages de bienvenue super().lancer() while self.vies > 0 and self.points_pour_gagner > self.points: # Obtient deux nombres aléatoires nombre1, nombre2 = self.obtenir_nombres_aleatoires() operation = f"{nombre1} x {nombre2}: " # Demande à l'utilisateur de répondre à cette opération # Empêche les erreurs de valeur reponse_utilisateur = self.obtenir_entree_numerique(message=operation) if reponse_utilisateur == nombre1 * nombre2: print("\nVotre réponse est correcte\n") # Ajoute un point self.points += 1 else: print("\nDésolé, votre réponse est incorrecte\n") # Soustrait une vie self.vies -= 1 self.afficher_score_actuel() self.afficher_vies_actuelles() # N'est exécuté que lorsque le jeu est terminé # Et aucune des conditions n'est vraie else: # Affiche le message final if self.points >= self.points_pour_gagner: self.afficher_message_gagne() else: self.afficher_message_perdu()
Voici une autre classe massive 😅. Mais comme je l’ai déjà mentionné, l’important n’est pas le nombre de lignes, mais plutôt la lisibilité et l’efficacité. L’avantage de Python est qu’il permet aux développeurs de créer un code propre et lisible, comme s’il s’agissait d’une conversation en langage naturel.
Cette classe comporte un point qui pourrait vous dérouter, mais je vais l’expliquer aussi simplement que possible.
# Classe parente def __init__(self, points_pour_gagner, nb_vies=3): "... # Classe enfant def __init__(self): # Le nombre de points nécessaires pour gagner est de 5 # Transmet l'argument 5 "points_pour_gagner" super().__init__(5)
Le constructeur de la classe enfant fait appel à la fonction super, qui fait à son tour référence à la classe parente (JeuDeBase). C’est en substance une instruction pour Python :
Remplissez l’attribut « points_pour_gagner » de la classe mère avec la valeur 5 !
Il n’est pas nécessaire d’inclure self à l’intérieur de la partie super().__init__(), car nous appelons super à l’intérieur du constructeur, ce qui entraînerait une redondance.
Nous utilisons également la fonction super dans la méthode lancer, et nous allons voir ce qui se passe dans ce bloc de code.
# Méthode lancer de base # Méthode parente def lancer(self): self.afficher_message_bienvenue() self.afficher_description() def lancer(self): # Appel de la classe supérieure pour afficher les messages de bienvenue super().lancer() .....
La méthode lancer de la classe parente affiche le message de bienvenue et la description. Cependant, il est judicieux de conserver cette fonctionnalité et d’y ajouter d’autres éléments dans les classes enfants. Par conséquent, nous utilisons super pour exécuter tout le code de la méthode parente avant d’exécuter la partie suivante.
Le reste de la fonction lancer est assez simple. Elle sollicite un nombre de l’utilisateur ainsi que le calcul de l’opération. Le résultat est ensuite comparé à la multiplication réelle. Si les deux sont égaux, un point est ajouté. Sinon, une vie est retirée.
Il est important de mentionner que nous utilisons des boucles while-else. Ceci dépasse le cadre de cet article, mais j’en publierai un dans quelques jours.
Enfin, la fonction obtenir_nombres_aleatoires utilise la fonction random.randint, qui renvoie un entier aléatoire compris dans l’intervalle spécifié. Elle renvoie ensuite un tuple de deux entiers aléatoires.
Classe de Révision des Tables de Multiplication
Le « deuxième mode » doit afficher le jeu sous forme de table de multiplication et s’assurer que l’utilisateur résout correctement au moins 2 tables.
Pour cela, nous allons à nouveau utiliser la puissance de super et modifier l’attribut points_pour_gagner de la classe mère, en lui attribuant la valeur 2.
class TableMultiplication(JeuDeBase): description = "Dans ce jeu, vous devez résoudre correctement la table de multiplication entière.nVous gagnez si vous résolvez 2 tables" def __init__(self): # Nécessite la résolution de 2 tables pour gagner super().__init__(2) def lancer(self): # Affiche les messages de bienvenue super().lancer() while self.vies > 0 and self.points_pour_gagner > self.points: # Obtient un nombre aléatoire nombre = random.randint(1, 10) for i in range(1, 11): if self.vies <= 0: # S'assure que le jeu ne continue pas # si l'utilisateur n'a plus de vies self.points = 0 break operation = f"{nombre} x {i}: " reponse_utilisateur = self.obtenir_entree_numerique(message=operation) if reponse_utilisateur == nombre * i: print("Bravo ! Votre réponse est correcte") else: print("Désolé, votre réponse est incorrecte") self.vies -= 1 self.points += 1 # N'est exécuté que lorsque le jeu est terminé # Et aucune des conditions n'est vraie else: # Affiche le message final if self.points >= self.points_pour_gagner: self.afficher_message_gagne() else: self.afficher_message_perdu()
Comme vous pouvez le constater, nous ne modifions que la méthode lancer de cette classe. C’est la magie de l’héritage : on écrit une fois la logique qu’on utilise à plusieurs endroits, et on l’oublie 😅.
Dans la méthode lancer, nous utilisons une boucle for pour obtenir les nombres de 1 à 10 et construisons l’opération qui est affichée à l’utilisateur.
De nouveau, si les vies sont épuisées ou si les points nécessaires pour gagner sont atteints, la boucle while se terminera et le message de gain ou de perte s’affichera.
BRAVO, nous avons créé les deux modes de jeu, mais jusqu’à présent, si nous exécutons le programme, rien ne se passera.
Finalisons donc le programme en intégrant le choix du mode, et en instanciant les classes en fonction de ce choix.
Mise en Œuvre du Choix
L’utilisateur pourra choisir le mode auquel il souhaite jouer. Voyons comment mettre cela en œuvre.
if __name__ == "__main__": print("Sélectionnez le mode de jeu") choix = input("[1],[2]: ") if choix == "1": jeu = MultiplicationAleatoire() elif choix == "2": jeu = TableMultiplication() else: print("Veuillez sélectionner un mode de jeu valide") exit() jeu.lancer()
Nous demandons à l’utilisateur de choisir entre les 2 modes. Si l’entrée n’est pas valide, le script s’arrête. Si l’utilisateur sélectionne le premier mode, le programme lancera le mode de jeu Multiplication Aléatoire, et s’il choisit le second, ce sera le mode Révision des Tables de Multiplication.
Voici ce à quoi cela ressemble.
Conclusion
Félicitations, vous venez de créer une application Python à l’aide de la programmation orientée objet.
L’intégralité du code est disponible dans le dépôt Github.
Dans cet article, vous avez appris à :
- Utiliser les constructeurs de classes Python
- Créer une application fonctionnelle avec la POO
- Utiliser la fonction super dans les classes Python
- Appliquer les concepts fondamentaux de l’héritage
- Mettre en œuvre des attributs de classe et d’instance
Bon codage 👨💻
Ensuite, découvrez quelques-uns des meilleurs IDE Python pour améliorer votre productivité.