Introduction : Pourquoi la POO ?
Salut ! Si tu es en spécialité NSI, tu as sûrement déjà écrit des fonctions et manipulé des listes ou des dictionnaires. C'est ce qu'on appelle la programmation procédurale. Mais pour des projets plus complexes, comme un jeu vidéo ou une application de gestion, une autre approche est bien plus puissante : la Programmation Orientée Objet (POO).
Imagine que tu développes un jeu. Tu as des personnages, des ennemis, des objets... Avec la POO, tu vas créer un modèle (une classe) pour chaque type d'élément. Chaque personnage du jeu sera alors une instance de la classe Personnage, avec ses propres caractéristiques (points de vie, position) et ses propres actions (attaquer, se déplacer). C'est une façon de penser ton code qui le rend plus organisé, plus facile à maintenir et à faire évoluer. C'est un pilier du programme de NSI et une compétence essentielle pour tout développeur.
Les bases : Créer sa première classe
Une classe est un plan de construction, un moule. Pour définir une classe en Python, on utilise le mot-clé class.
La méthode __init__ et les attributs
La méthode spéciale __init__ est le constructeur. C'est elle qui est appelée automatiquement quand tu crées un nouvel objet (une instance) de la classe. C'est ici que tu vas initialiser les attributs, les variables qui caractérisent l'objet.
Prenons un exemple concret : une classe EtudiantNSI.
class EtudiantNSI:
def __init__(self, nom, prenom, moyenne):
self.nom = nom
self.prenom = prenom
self.moyenne = moyenne
self.notes = [] # Un attribut avec une valeur par défaut
def ajouter_note(self, nouvelle_note):
"""Une méthode pour modifier l'état de l'objet."""
self.notes.append(nouvelle_note)
self.moyenne = sum(self.notes) / len(self.notes)
def est_admis(self):
"""Une méthode qui renvoie une information."""
return self.moyenne >= 10
# Création d'instances
alice = EtudiantNSI("Martin", "Alice", 15)
bob = EtudiantNSI("Durand", "Bob", 8)
# Utilisation des méthodes
alice.ajouter_note(17)
print(f"{alice.prenom} est admis ? {alice.est_admis()}") # Affiche : Alice est admis ? True
print(f"{bob.prenom} est admis ? {bob.est_admis()}") # Affiche : Bob est admis ? FalsePoints clés à retenir :
- self : représente l'instance courante. Il doit être le premier paramètre de chaque méthode d'instance.
- Attributs (comme
self.nom) : ce sont les données de l'objet. - Méthodes (comme
ajouter_note) : ce sont les fonctions qui définissent le comportement de l'objet.
L'héritage : Éviter la répétition et spécialiser
L'héritage est un concept fondamental de la POO. Il permet de créer une nouvelle classe (classe fille) à partir d'une classe existante (classe mère ou parente). La classe fille hérite de tous les attributs et méthodes de la classe mère, et peut en ajouter de nouveaux ou en redéfinir.
Pourquoi c'est génial ? Pour la réutilisation du code et la spécialisation.
Exemple : Dans ton jeu, tu as une classe Personnage générique. Les Guerrier et les Mage sont des types de personnages particuliers.
class Personnage:
def __init__(self, nom, points_de_vie):
self.nom = nom
self.pv = points_de_vie
def attaquer(self, cible):
print(f"{self.nom} attaque {cible.nom} !")
class Guerrier(Personnage): # Guerrier hérite de Personnage
def __init__(self, nom, points_de_vie, force):
# On appelle le constructeur de la classe parente
super().__init__(nom, points_de_vie)
# On ajoute un attribut spécifique au Guerrier
self.force = force
# On redéfinit (surcharge) la méthode attaquer
def attaquer(self, cible):
degats = self.force * 2
cible.pv -= degats
print(f"{self.nom} frappe violemment {cible.nom} et lui inflige {degats} dégâts !" )
class Mage(Personnage):
def __init__(self, nom, points_de_vie, mana):
super().__init__(nom, points_de_vie)
self.mana = mana
def lancer_sort(self, cible):
if self.mana >= 10:
self.mana -= 10
cible.pv -= 15
print(f"{self.nom} lance un sort sur {cible.nom} !")
# Utilisation
conan = Guerrier("Conan", 100, 25)
merlin = Mage("Merlin", 80, 50)
conan.attaquer(merlin) # Utilise la méthode de Guerrier
print(f"PV de Merlin : {merlin.pv}")
merlin.lancer_sort(conan) # Méthode spécifique au Mage
print(f"PV de Conan : {conan.pv}")L'héritage crée une hiérarchie logique et évite de dupliquer le code commun (comme nom et pv).
Concepts avancés : Polymorphisme et encapsulation
Le polymorphisme
Ce terme un peu barbare signifie "qui peut prendre plusieurs formes". En pratique, grâce à l'héritage, tu peux traiter un objet d'une classe fille comme un objet de la classe mère. Par exemple, tu peux avoir une liste de Personnage contenant des Guerrier et des Mage, et les faire tous attaquer via une boucle. Chaque objet utilisera sa propre version de la méthode attaquer : c'est le polymorphisme.
L'encapsulation
C'est le principe qui consiste à protéger les données internes d'un objet. En Python, c'est plus une convention qu'une règle stricte. On utilise :
- Un seul underscore
_attribut: pour signaler qu'un attribut est "protégé" (à usage interne). Il reste accessible, mais c'est une indication pour le programmeur. - Deux underscores
__attribut: pour "mangler" le nom de l'attribut et le rendre plus difficile à accéder accidentellement depuis l'extérieur de la classe. C'est une forme de protection faible.
L'idée est de fournir des méthodes publiques (interface) pour interagir avec l'objet, plutôt que de manipuler ses attributs directement. Cela garantit un meilleur contrôle.
Projet NSI : Modéliser une bibliothèque
Mettons tout ça en pratique avec un mini-projet typique de NSI : un système de gestion de bibliothèque.
class Livre:
def __init__(self, titre, auteur, isbn):
self.titre = titre
self.auteur = auteur
self.isbn = isbn
self.disponible = True
def emprunter(self):
if self.disponible:
self.disponible = False
return True
return False
def rendre(self):
self.disponible = True
class Utilisateur:
def __init__(self, nom, id_user):
self.nom = nom
self.id = id_user
self.livres_empruntes = []
def emprunter_livre(self, livre):
if livre.emprunter():
self.livres_empruntes.append(livre)
print(f"{self.nom} a emprunté '{livre.titre}'.")
else:
print(f"Le livre '{livre.titre}' n'est pas disponible.")
# Création des objets
livre1 = Livre("Le Petit Prince", "Saint-Exupéry", "12345")
livre2 = Livre("1984", "George Orwell", "67890")
alice = Utilisateur("Alice", "U001")
# Simulation
alice.emprunter_livre(livre1)
alice.emprunter_livre(livre2)
alice.emprunter_livre(livre1) # Tentative de ré-emprunt
livre1.rendre()
print(f"{livre1.titre} est disponible : {livre1.disponible}")Ce modèle simple peut être étendu avec une classe Bibliotheque qui gérerait des listes de livres et d'utilisateurs, ou avec un héritage pour différencier les types de livres (Roman, BD, Manuel).
La POO, c'est avant tout une nouvelle façon de penser ton code. Commence par identifier les entités (les objets) de ton problème, puis modélise-les avec des classes. Avec un peu de pratique, cela deviendra naturel et te permettra de réaliser des projets NSI bien plus ambitieux et structurés !
