Aller au contenu

Programmation Orientée Objet

1) Introduction : Programmation procédurale, programmation orientée objet⚓︎

1.1) La notion d’objet et de classe⚓︎

Jusqu’ici, les programmes ont été réalisés en programmation procédurales, c’est à dire que chaque programme a été décomposé en plusieurs fonctions réalisant des tâches simples.
Cependant lorsque plusieurs programmeurs travaillent simultanément sur un projet, il est nécessaire de programmer autrement afin d’éviter les conflits entre les fonctions pouvant porter le même nom sans le savoir. On utilise pour cela la Programmation Orientée Objet.

Note

Un objet se caractérise par :

  • son identité : son nom
  • son état : défini par la valeurs de ses attributs
  • son comportement : défini par ses méthodes

1.2) Classe : un premier exemple avec le type list⚓︎

1
2
3
4
liste = [1, 5, 3]   # instanciation d'un objet liste
print("Type de l'ojet liste :",type(liste))
liste.sort()      # exécution de la méthode sort() sur l'objet liste
print("Contenu de l'objet liste :",liste)

Type de l'ojet liste : <class 'list'\>
Contenu de l'objet liste : [1, 3, 5]

Une action possible sur les objets de type liste est le tri de celle-ci avec la méthode nommée sort().

La syntaxe est : nom_objet.nom_méthode(), comme avec la méthode de tri : liste.sort().

Rappel sur le vocabulaire dédié.

  • Les variables d'une classe s'appellent des attributs
  • Les fonctions d'une classe s'appellent des méthodes
  • Un objet est une instance d'une classe
  • La création de l'objet s'appelle l'instanciation d'une classe
  • La méthode exécutée automatiquement lors de l'instanciation d'un objet s'appelle le constructeur
  • On dit que les attributs et les méthodes sont encapsulés dans la classe.

1.3) Un peu d'histoire.⚓︎

La programmation orientée objet, qui fait ses débuts dans les années 1960 avec les réalisations dans le langages Lisp, a été formellement définie avec les langages Simula (vers 1970) puis SmallTalk.
Puis elle s’est développée dans les langages anciens comme le Fortran, le Cobol et est même incontournable dans des langages récents comme Java.

2) Création d'une classe pas à pas.⚓︎

2.1) Un constructeur.⚓︎

On va créer une classe simple, la classe Carte correspondant à une carte d’un jeu de 32 ou 52 cartes.
Par convention, une classe s’écrit toujours avec une majuscule.

1
2
3
4
5
class Carte:                            # Définition de la classe
    """Une carte d'un jeu de 32 ou de 52 cartes"""
    def __init__(self, valeur, couleur): # méthode 1 : le constructeur
        self.valeur = valeur        # 1er attribut valeur {de 2 à 14 pour as}
        self.couleur = couleur      # 2e attribut {'pique','carreau','coeur','trefle'} 

L'attribut self

La variable self , dans les méthodes d’un objet, désigne l’objet auquel s’appliquera la méthode.
Elle représente l’objet dans la méthode en attendant qu’il soit créé.

Création d'une instance de la classe carte :

1
2
carte1 = Carte(5,'carreau')  # Conventions :le nom de la classe commence par une majuscule, 
                             #              le nom de l'objet commence par une minuscule.

Accès aux attributs de l'objet carte1 : on utilise un point, on peut le lire de droite à gauche en remplaçant le point par "de" : * carte1.valeur : valeur de carte1 * carte1.couleur : couleur de carte1

1
print(carte1.valeur, 'de', carte1.couleur)

5 de carreau

1
2
3
4
carte2 = Carte(3, 'trefle')   # Dans ce cas, le self de la classe Carte fait référence à carte2
carte3 = Carte(9, 'coeur')    # Dans ce cas, le self de la classe Carte fait référence à carte3
print(carte2.valeur, 'de', carte2.couleur)
print(carte3.valeur, 'de', carte3.couleur)

3 de trefle
9 de coeur

2.2) Encapsulation : les accesseurs ou "getters".⚓︎

L'encapsulation désigne le fait de regrouper l'ensemble des attributs et des méthodes dans une même classe.

But de l'Encapsulation : * simplifier la vie du programmeur qui les utilise; * masquer leur complexité (diviser pour régner - pensez à la méthode list.sort() dont l'écriture est très complexe); * permettre de modifier une classe sans changer les programmes qui l'utilisent; * la liste des méthodes devient une sorte de mode d’emploi de la classe.

Les accesseurs - getters
On ne va généralement pas utiliser la méthode précédente nom_objet.nom_attribut pour d’accéder aux valeurs des attributs car on ne veut pas forcément que l’utilisateur ait accès à la représentation interne des classes.
Pour utiliser ou modifier les attributs, on utilisera de préférence des méthodes dédiées : les accesseurs (ou getters).

Le nom d’un accesseur est généralement : getNom_attribut().

La méthode __str__(self) renvoie une chaîne de caractère représentant l'objet. On y accède automatiquement lors de l'exécution de l'instruction print(nom_de_l_objet).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Carte: # Définition de la classe
    "Une carte d'un jeu de 32 ou 52 cartes"
    def __init__(self, valeur, couleur): # méthode 1 : constructeur
        self.valeur = valeur # 1er attribut valeur {de 2 à 14 pour as}
        self.couleur = couleur # 2e attribut {'pique','carreau','coeur','trefle'}
        
    def getAttributs(self): # méthode 2 : permet d'accéder aux attributs
        return (self.valeur, self.couleur)
    
    def __str__(self):
        """ 
            Renvoie automatiquement une chaîne de caractères représentant l'objet 
            lors de l'exécution de l'instrcution "print(nom_de_l_objet)
        """
        return f"{self.valeur} de {self.couleur}"

    
carte1 = Carte(10,'pique')
print(carte1.getAttributs())
print(carte1)

(10, 'pique')
10 de pique

Exercice 1⚓︎

Créer deux autres méthodes permettant de récupérer la valeur de la carte et la couleur avec les "getters" (accesseurs) : getCouleur() et getValeur().
Les utiliser pour afficher : carte1 est un 10 de pique

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Carte: # Définition de la classe
    "Une carte d'un jeu de 32 ou 52 cartes"
    def __init__(self, valeur, couleur): # méthode 1 : constructeur
        self.valeur = valeur # 1er attribut valeur {de 2 à 14 pour as}
        self.couleur = couleur # 2e attribut {'pique','carreau','coeur','trefle'}
        
    def getAttributs(self): # méthode 2 : permet d'accéder aux attributs
        """
            renvoie les attributs de la classe
            :return: tuple contenant la valeur et la couleur de la carte 
            :rtype: tuple : (int, str)
        """
        return (self.valeur, self.couleur)
    
    def __str__(self):
        """ 
            Renvoie automatiquement une chaîne de caractères représentant l'objet 
            lors de l'exécution de l'instrcution "print(nom_de_l_objet)
        """
        return f"{self.valeur} de {self.couleur}"
     
carte1 = Carte(10,'pique')

2.3) Modifications contrôlées des valeurs des attributs : les mutateurs ou "setters"⚓︎

On va devoir contrôler les valeurs attribuées aux attributs. Pour cela, on passe par des méthodes particulières appelées mutateurs (ou "setters") qui vont modifier la valeur d’un attribut d’un objet.

Le nom d’un mutateur est généralement : setNom_attribut().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Carte: # Définition de la classe
    "Une carte d'un jeu de 32 ou 52 cartes"
    def __init__(self, valeur, couleur):   # constructeur
        self.valeur = valeur # 1er attribut {de 2 à 14}
        self.couleur = couleur # {'pique', 'carreau', 'coeur', 'trefle'}

    def getAttributs(self): # méthode 2 : accesseur
        """
            renvoie les attributs de la classe
            :return: tuple contenant la valeur et la couleur de la carte 
            :rtype: tuple : (int, str)
        """
        return (self.valeur, self.couleur)
    
    def __str__(self):
        """ 
            Renvoie automatiquement une chaîne de caractères représentant l'objet 
            lors de l'exécution de l'instrcution "print(nom_de_l_objet)"
            
            :example:
            >>> carte2 = Carte(7,'coeur')
            >>> print(carte2)
            7 de coeur
        """
        return f"{self.valeur} de {self.couleur}"

    def setValeur(self, valeur): # mutateur avec contrôle
        """
            Si valeur est compris entre 2 et 14 compris, attribut cette valeur 
            à self.valeur et renvoie True. Sinon, renvoie False uniquement.
        """
        if isinstance(valeur, int):
            if 2 <= valeur <= 14:
                self.valeur = valeur
                return True
        return False
    

carte2 = Carte(7,'coeur')
print(carte2.getAttributs())
if not(carte2.setValeur(15)): 
    print("la valeur de la carte doit être un nombre compris entre 2 et 14")
print(carte2.getAttributs())
if not(carte2.setValeur(10)): 
    print("la valeur de la carte doit être un nombre compris entre 2 et 14")
print(carte2.getAttributs())
print(carte2)

(7, 'coeur')
la valeur de la carte doit être un nombre compris entre 2 et 14
(7, 'coeur')
(10, 'coeur')
10 de coeur

Exercice 2⚓︎

  1. Créer le mutateur de l’attribut couleur sous la forme setCouleur(self,c).
  2. Contrôler l'attribut pour qu'il puisse prendre uniquement une des 4 couleurs.
  3. Créer une carte carte3, un Roi de coeur.
  4. Modifier la couleur de la carte carte3 en la passant à pique. Afficher la carte.
  5. Tenter de modifier la couleur de la carte carte3 en la passant à losange. Afficher la carte.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Carte: # Définition de la classe
    "Une carte d'un jeu de 32 ou 52 cartes"
    def __init__(self, valeur, couleur):   # constructeur
        self.valeur = valeur # 1er attribut {de 2 à 14}
        self.couleur = couleur # {'pique', 'carreau', 'coeur', 'trefle'}

    def getAttributs(self): # méthode 2 : accesseur
        return (self.valeur, self.couleur)
    
    def __str__(self):
        """ 
            Renvoie automatiquement une chaîne de caractères représentant l'objet 
            lors de l'exécution de l'instrcution "print(nom_de_l_objet)
        """
        return f"{self.valeur} de {self.couleur}"

    def setValeur(self, v): # mutateur avec contrôle
        if isinstance(v, int):
            if 2 <= v <= 14:
                self.valeur = v
                return True
        return False

3) Notion d'agrégation⚓︎

Lorsqu'on a écrit la classe Carte et qu'on veut l'utiliser dans un programme, on va utiliser la notion de module python. Autrement dit on va sauvegarder le code de la classe Carte dans un fichier carte.py et importer le module carte dans le nouveau programme. Pour cela, on va : 1. Enregistrer le code de la classe Carte dans un fichier carte.py, dans le même dossier. 2. Commencer le nouveau programme en tapant : from carte import *.

Par exemple, on va créer un jeu de carte qui utilise la classe Carte.
Il pourra avoir 32 ou 52 cartes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# Importer ci-dessous le module carte.py

class Jeu_de_cartes:
    def __init__(self, nombre_cartes = 32):   # par défaut nombre_cartes vaut 32. 
        """
            Constructeur : crée un jeu de "nombre_cartes" cartes lors de l'instanciation de la classe JeuDeCarte.
            Si "nombre_cartes" est différent de 32 ou 52 alors "nombre_cartes" vaut 32
            Exécute pour cela la méthode creerPaquet()
            
            :param nombre_cartes: nombre de cartes du jeu, 32 ou 52 sinon nombre_cartes = 32
            :type nombre_cartes: int
        """
        self.nombre_cartes = nombre_cartes
        self.paquet_cartes = []
    
    
    def get_nombre_cartes(self):
        """
           Accesseur de l'attribut self.nombre_cartes : renvoie le nombre de cartes dans le jeu
           
           :return: nombre de cartes du jeu
           :rtype: int
        """
        pass     # à compléter    
    

    def get_paquet(self):
        """
            Accesseur de l'attribut self.paquet_cartes : renvoie la liste des cartes encore dans le jeu
           
            :return: liste des cartes encore présente dans le jeu
            :rtype: list
        """
        pass     # à compléter
    
    
    def creer_paquet(self):
        """
            vide la liste "self.paquet_cartes" puis la remplit de "self.nombre_cartes" cartes.
            Cette méthode ne prend aucun paramètre et ne renvoie rien.
        """
        pass
    

    def distribuer_une_carte(self):
        """
           Renvoie la première carte du paquet (la dernière de la liste, elle est plus facile d'accès)
           et la supprime du paquet
           
           :return: la première carte du paquet
           :rtype: carte
        """
        pass
    

    def melanger_jeu(self):
        """
           Mélange de façon pseudo-aléatoire les cartes du paquet
           Cette méthode ne prend aucun paramètre et ne renvoie rien.
        """
        pass

Excercice 4⚓︎

Importation du module carte.py 1. Enregistrer le code de la classe Carte dans un fichier carte.py, dans le même dossier. 2. Commencer le nouveau programme en tapant : from carte import *.

Implémentation du constructeur

  1. Modifier la ligne de code pour s'assurer que self.nombre_cartes est égale à 32 ou 52, sinon l'imposer à 32.
  2. Ajouter la ligne de code qui appelle la méthode creer_paquet() qui créer le jeu de cartes.
  3. Implémenter la méthode creer_paquet(). Tester son fonctionnement.
  4. Créer la méthode __str__(self) qui permet d'afficher les cartes du jeu. Servez vous de la méthode __str__(self) de la Classe Carte.
  5. Instancier un jeu de 32 cartes jeu1 et afficher les cartes qu'il contient avec l'instruction print(jeu1).

Compléter les autres méthodes de la classe.

  1. Implémenter les accesseurs et tester leur fonctionnement.
  2. Implémenter la méthode melanger_jeu(self) et tester son fonctionnement. Utiliser la méthode list.shuffle().
  3. Implémenter la méthode distribuer_une_carte(self) et tester son fonctionnement (en particulier le fait que la carte soit supprimée du paquet).