1. Les concepts de base de l’approche orientée objet

Presque tous les programmes et techniques de programmation que vous avez utilisés jusqu’à présent relèvent du style procédural de programmation, même s’il vous est arrivé d’utilisé certains objets intégrés en vous référant à eux.

Le style procédural de programmation a été l’approche dominante du développement logiciel pendant des décennies d’informatique, et il est toujours utilisé aujourd’hui. De plus, il ne disparaîtra pas à l’avenir, car il fonctionne très bien pour des types de projets spécifiques (généralement, pas très complexes et pas grands, mais il y a beaucoup d’exceptions à cette règle).

L’approche objet est assez jeune (beaucoup plus jeune que l’approche procédurale) et est particulièrement utile lorsqu’elle est appliquée à des projets grands et complexes réalisés par de grandes équipes composées de nombreux développeurs.

Ce type de compréhension de la structure d’un projet facilite de nombreuses tâches importantes, par exemple la division du projet en petites parties indépendantes et le développement indépendant de différents éléments du projet.

Python est un outil universel pour la programmation d’objets et de procédures. Il peut être utilisé dans les deux cas.

De plus, vous pouvez créer de nombreuses applications utiles, même si vous ne savez rien des classes et des objets, mais vous devez garder à l’esprit que certains des problèmes (par exemple, la gestion de l’interface utilisateur graphique) peuvent nécessiter une approche d’objet stricte.

Heureusement, la programmation d’objets est relativement simple.

2. Comparaison des approches procédurale et objet

Dans l’approche procédurale, il est possible de distinguer deux mondes différents et complètement séparés:

  • le monde des données, peuplé de variables de différents types

  • le monde du code, habité par du code regroupé en modules et fonctions.

Les fonctions peuvent utiliser des données, mais pas l’inverse. De plus, les fonctions sont capables d’abuser des données, c’est-à-dire d’utiliser la valeur d’une manière non autorisée (par exemple, lorsque la fonction sinus obtient un solde de compte bancaire comme paramètre).

Nous avons dit dans le passé que les données ne pouvaient pas utiliser de fonctions. Mais est-ce tout à fait vrai? Existe-t-il des types particuliers de données qui peuvent utiliser des fonctions?

Oui, il y a - celles nommées méthodes. Ce sont des fonctions qui sont appelées à l’intérieur des données, pas à côté d’elles. Si vous pouvez voir cette distinction, vous avez fait le premier pas dans la programmation d’objets.

>>> texte = "bonjour le monde !"
>>> texte_en_majuscule = texte.upper()
>>> print(texte_en_majuscule)
BONJOUR LE MONDE !

L’approche objet suggère une façon de penser complètement différente :

  • Les données et le code sont enfermés ensemble dans le même monde, divisés en classes.

  • Chaque classe est comme une recette qui peut être utilisée lorsque vous souhaitez créer un objet utile (c’est de là que vient le nom de l’approche).

  • Vous pouvez produire autant d’objets que nécessaire pour résoudre votre problème.

  • Chaque objet a un ensemble de caractéristiques propres (ils sont appelés propriétés ou attributs - nous utiliserons les deux mots de manière synonyme) et est capable d’effectuer un ensemble d’actions (appelées méthodes).

  • Les diagrammes de classe expriment la structure statique du système en termes de classes et de relations entre ces classes. On y distingue pour chaque classe, son nom, ses propriétés ou attributs et ses méthodes dans un bloc compartimenté.

DiagrammeClasse
Figure 1. Diagramme de classes
  • Les recettes peuvent être modifiées si elles sont inadéquates à des fins spécifiques et, en effet, de nouvelles classes peuvent être créées. Ces nouvelles classes héritent des propriétés et des méthodes des originaux, et en ajoutent généralement de nouvelles, créant de nouveaux outils plus spécifiques.

Objets et classes
  • Une classe est un modèle conceptuel qui regroupe des attributs et des méthodes communes à un ensemble d’objets et permettant de créer des objets ayant ces propriétés. C’est une "fabrique d’objets".

  • Les objets sont des incarnations d’idées exprimées en classe, comme un cheesecake dans votre assiette est une incarnation de l’idée exprimée dans une recette imprimée dans un vieux livre de cuisine. On appelle également un objet une instance de classe et l’opération de construction d’un objet l’instanciation.

  • Les objets interagissent entre eux, échangeant des données ou activant leurs méthodes. Une classe correctement construite (et donc ses objets) est capable de protéger les données sensibles et de les cacher des modifications non autorisées.

  • Il n’y a pas de frontière claire entre les données et le code: ils vivent comme un dans les objets.

  • Tous ces concepts ne sont pas aussi abstraits que vous ne le pensez au premier abord. Au contraire, ils sont tous tirés d’expériences de la vie réelle, et sont donc extrêmement utiles dans la programmation informatique: ils ne créent pas de vie artificielle - ils reflètent des faits, des relations et des circonstances réels.

exemple1
Figure 2. Exemple très simple de diagramme de classes

3. Hiérarchie des classes

Observons le diagramme de classes suivant :

vehicules
Figure 3. Hiérarchie des classes

Tous les véhicules existants (et ceux qui n’existent pas encore) sont liés par une seule caractéristique importante : la capacité de se déplacer. Vous pouvez dire qu’un chien bouge aussi; un chien est-il un véhicule? Non, il ne l’est pas. Nous devons améliorer la définition, c’est-à-dire l’enrichir avec d’autres critères, distinguer les véhicules des autres êtres et créer une connexion plus forte. Prenons en considération les circonstances suivantes : les véhicules sont des entités créées artificiellement utilisées pour le transport, déplacées par les forces de la nature et dirigées (conduites) par les humains.

Sur la base de cette définition, un chien n’est pas un véhicule.

La classe des véhicules est très large. Trop large. Nous devons alors définir des classes plus spécialisées :

  • Les classes spécialisées sont les sous-classes.

  • La classe des véhicules sera une superclasse pour tous.

La hiérarchie croît de haut en bas, comme les racines des arbres :

  • La classe la plus générale et la plus large est toujours en haut (la superclasse),

  • ses descendants sont situés en dessous (les sous-classes).

On pourrait probablement trouver et organiser d’autres sous-classes potentielles pour la superclasse Véhicules. Il existe de nombreuses classifications possibles. Nous avons choisi des sous-classes basées sur l’environnement :

  • véhicules terrestres;

  • véhicules nautiques;

  • véhicules aériens;

  • véhicules spatiaux.

Dans cet exemple, la première sous-classe uniquement est à son tour spécialisée.

Les véhicules terrestres peuvent être divisés davantage, selon la méthode avec laquelle ils impactent le sol. Ainsi, nous pouvons énumérer:

  • véhicules à roues;

  • véhicules à chenilles;

  • aéroglisseurs.

Direction des flèches
  • Elles pointent toujours vers la superclasse et matérialise une relation d’héritage.

  • La sous-classe (spacialisée) hérite de la super-classe (généralisée).

  • La classe de niveau supérieur est une exception - elle n’a pas de superclasse.

3.1. Travail à faire

Imaginez quelles classes pourraient spécialiser les classes restantes.

4. Qu’est-ce qu’un objet ?

  • Une classe est un ensemble d’objets.

  • Un objet est une entité appartenant à une classe.

  • Un objet est une incarnation des attributs et des méthodes d’une classe spécifique.

  • Lorsque les classes forment une hiérarchie, un objet appartenant à une classe spécifique appartient à toutes ses superclasses en même temps.

Par exemple : toute voiture personnelle est un objet appartenant à la classe des véhicules à roues. Cela signifie également que la même voiture appartient à toutes les superclasses de sa classe d’origine; par conséquent, elle fait également partie de la classe des véhicules. Votre chien (ou votre chat) est un objet de la classe des mammifères domestiques, ce qui signifie explicitement qu’il est également inclus dans la classe des animaux.

  • Chaque sous-classe est plus spécialisée que sa superclasse.

  • Inversement, chaque superclasse est plus générale (plus abstraite) que n’importe laquelle de ses sous-classes.

4.1. Que possède un objet ?

Les conventions de programmation objet suppose que chaque objet existant possède :

  • un nom qui l’identifie de manière unique dans son espace de noms d’origine

  • un ensemble de propriétés individuelles qui le rendent original, unique ou exceptionnel

  • un ensemble de méthodes pour effectuer des actions spécifiques, capable de changer l’objet lui-même, ou certains des autres objets.

Par convention, chaque fois que vous décrivez un objet, vous devez utiliser :

  • un nom : pour définir le nom de l’objet;

  • un adjectif : pour définir une propriété de l’objet;

  • un verbe : pour définir une action de l’objet.

Une classe est un modèle d’une partie très spécifique de la réalité, reflétant des propriétés et des activités trouvées dans le monde réel. Les objets sont définis selon notre propre expérience. Par exemple, la phrase suivante doit conduire à création d’un modèle de programmation objet :

Max est un gros chat qui dort toute la journée.

  • Nom de l’objet : Max

  • Classe : chat

  • Attribut : taille = gros

  • Méthode : dormir(toute la journée)

max
Figure 4. Conceptualisation objet/classe

4.2. Travail à faire : Définir un objet

Définissez un objet à partir de la phrase suivante :

Une Ferrari Testarossa rouge roule vite

4.3. L’héritage

L’héritage une relation de spécialisation/généralisation.

  • Les éléments spécialisés héritent de la structure et du comportement des éléments plus généraux (propriétés, associations et autres héritages)

  • Les objets d’une sous-classe héritent des attributs et des méthodes de ses superclasses.

  • Exemple : Par héritage de la classe Article, un livre a d’office un prix, une désignation et une opération acheter(), sans qu’il soit nécessaire de le préciser. Il est de même pour les objets des classes Disque et Video. La classe Disque ajoute une méthode ecouter() à ses objets.

heritage

5. Première classe

  • Création d’une classe vide en python :

class SimpleClasse:
    pass
  • La définition d’une classe commence par l’utilisation du mot clé : Class

  • Le nom de la classe suit le mot clé class

  • Terminer avec les deux points qui marquent le début du bloc de définition de la classe

6. Premier objet

La classe définie devient un outil capable de créer de nouveaux objets. L’outil doit être utilisé explicitement, sur demande, pour créer un objet qui sera affecter une variable pour stocker en mémoire :

class SimpleClasse:
    pass

un_objet = SimpleClasse()

6.1. Travail à faire : Objet en mémoire

  • Créer la classe SimpleClasse et l’objet un_objet.

  • Afficher l’objet un_objet et observer le résultat :

Le message renvoyé par python indique que un_objet contient une référence à une instance de la classe SimpleClasse, qui est définie elle-même au niveau principal du programme. Elle est située dans un emplacement bien déterminé de la mémoire vive, dont l’adresse apparaît ici en notation hexadécimale.

6.2. Le constructeur

La création d’un objet de la classe se fait en appelant une méthode spéciale et implicite que l’on nomme constructeur. Cette méthode porte le même nom que la classe et peut être redéfinie dans la classe.

Le constructeur sert en particulier à définir l’état initial de l’objet et peut accepter des paramètres.

Un constructeur n’est rien d’autre qu’une méthode, sans valeur de retour, qui porte un nom imposé par le langage Python : _init_().

Cette méthode sera appelée lors de la création de l’objet. Le constructeur peut disposer d’un nombre quelconque de paramètres : _init_(self, param1, param2, …​):

Cependant, le premier paramètre d’une méthode doit être self :

Exemple
class Chat:
  def __init__(self,nom):
    self.nom = nom

  def dormir(self, duree):
    return duree

chat1 = Chat("Max")
chat1.taille = "gros"

print(chat1.nom + " est un " + chat1.taille + " chat qui dort " + chat1.dormir("beaucoup"))

Remarquez que l’attribut taille appartient à l’objet chat1 et n’est pas définit dans la classe Chat. Python autorise la création d’attributs propre à un objet en dehors de sa classe, ce n’est pas le cas d’autres langages.

En général, il vaut mieux définir les attributs dans la classe :

Exemple
class Chat:
  def __init__(self,nom):
    self.nom = nom

  def dormir(self, duree):
    return duree

  def qui_je_suis(self):
    return self.nom + " est un " + self.taille + " chat qui dort " + self.dormir("beaucoup")

chat1 = Chat("Max")
chat1.taille = "gros"

chat2 = Chat("Minou")
chat2.taille = "petit"

print(chat1.qui_je_suis())
print(chat2.qui_je_suis())

6.3. Travail à faire : Créer une classe et des objets

  • Créer la classe Voiture qui possède les attributs et le méthodes suivantes :

    • Attributs :

      • marque : Chaine de caractères

      • modele : Chaine de caractères

      • couleur: Chaine de caractères

    • Méthodes :

      • Constructeur : initialise les attributs avec ses paramètres

      • roule(vitesse) : retourne la valeur du paramètre vitesse qui est une chaine de caractères

      • descriptiton() : retourne une phrase sur le modèle : "la Ferrari Testarossa de couleur rouge roule vite"

  • Créer un objet voiture1 qui roule vite et afficher sa description :

    • marque : Ferrari

    • modele : Testarossa

    • couleur: rouge

  • Créer un objet voiture2 qui roule lentement et afficher sa description :

    • marque : Ligier

    • modele : JS50

    • couleur: bleu

6.4. La notion d’encapsulation

Le concept d”encapsulation est un concept très utile de la programmation orienté objets. Il permet en particulier d’éviter une modification par erreur des données d’un objet. En effet, il n’est alors plus possible d’agir directement sur les données d’un objet, il est nécessaire de passer par ses méthodes qui jouent le rôle d’interface obligatoire.

On parle dans ce cas d’attributs privés

La protection des attributs d’une classe est réalisée grâce à l’utilisation d’attributs privées.

Pour avoir des attributs privés, leur nom doit débuter par __ (deux fois le symbole underscore).

Si le nom d’un attributs ne commence pas par __, il est public et donc accessible directement, y dehors de la définition de la classe.

Exemple
class Chat:
  def __init__(self,nom):
    self.__nom = nom
    self.__taille = ""

  def dormir(self, duree):
    return duree

  def qui_je_suis(self):
    return self.__nom + " est un " + self.__taille + " chat qui dort " + self.dormir("beaucoup")

chat1 = Chat("Max")
chat1.__taille = "gros"

chat2 = Chat("Minou")
chat2.__taille = "petit"

print(chat1.qui_je_suis())
print(chat2.qui_je_suis())

L’affectation de l’attibut __taille dans le programme n’a eu aucun effet. Il est protégé contre les modifications directes. On peut ici ajouter un paramètre au constructeur pour initialiser cet attribut lors de la construction de l’objet et/ou ajouter un méthode pour y accéder :

Exemple
class Chat:
  def __init__(self,nom, taille = "petit"):
    self.__nom = nom
    self.__taille = taille

  def dormir(self, duree):
    return duree

  def qui_je_suis(self):
    return self.__nom + " est un " + self.__taille + " chat qui dort " + self.dormir("beaucoup")

  def set_taille(self, taille):
    self.__taille = taille

chat1 = Chat("Max", "gros")

chat2 = Chat("Minou")

print(chat1.qui_je_suis())
print(chat2.qui_je_suis())

chat1.set_taille("très gros")
print(chat1.qui_je_suis())
Accesseurs et mutateurs

Parmi les différentes méthodes que comporte une classe, on a souvent tendance à distinguer :

  • les constructeurs

  • les accesseurs (en anglais accessor) qui fournissent des informations relatives à l’état d’un objet, c’est-à-dire aux valeurs de certains de ses attributs (généralement privés) sans les modifier

  • les mutateurs (en anglais mutator) qui modifient l’état d’un objet, donc les valeurs de certains de ses attributs.

On rencontre souvent l’utilisation de noms de la forme get_XXXX() pour les accesseurs et set_XXXX() pour les mutateurs, y compris dans des programmes dans lesquels les noms de variable sont francisés.

7. Utilisation des objets

7.1. Travail à faire : Créer une classe et des objets

  • Créer la classe Complexe :

    • Attributs privés :

      • __partie_reel : nombre réel

      • __partie_imaginaire : nombre réel

    • Méthodes :

      • Constructeur : permet de créer un objet de la classe en initialisant tous les attibuts.

      • get_partie_reel() : retourne la valeur de l’attribut __partie_reel

      • get_partie_imaginaire() : retourne la valeur de l’attribut __partie_imaginaire

      • set_partie_reel() : modifie la valeur de l’attribut __partie_reel

      • set_partie_imaginaire() : modifie la valeur de l’attribut __partie_imaginaire

      • module() : retourne la valeur du module du nombre complexe (Calculer le module)

      • argument() : retourne la valeur de l’argument du nombre complexe (Calculer l’argument)

      • algebrique() : retourne la forme algébrique du nombre complexe comme 3 + 2i ou 3 - 2i

      • additionner(z) : retourne la somme du nombre complexe courant et du nombre complex z

  • créer les objets suivant de la classe Complexe :

    • z1 = 3 + i

    • z2 = 2 - 4i

    • z3 = z1 + z2

    • z1 = z1 + z2

  • Pour chaque objet, afficher la forme algébrique, le module et l’argument

8. Execices autocorrigés


  • ___ représente une entité dans le monde réel avec son identité et son comportement.

  1. Une méthode
  2. Une classe
  3. Un objet

  • ___ est utilisé(e) pour créer un objet :

  1. Le constructeur
  2. La classe
  3. Une fonction

  • Quelle est la sortie de cet extrait de code :

class test:
     def __init__(self,a="Hello World"):
         self.a=a

     def display(self):
         print(self.a)
obj=test()
obj.display()
  1. Erreur : le constructeur ne peut pas avoir d'arguments par défaut
  2. “Hello World” est affiché
  3. Rien ne s'affiche

  • Quelle est la sortie de cet extrait de code :

class change:
    def __init__(self, x, y, z):
        self.a = x + y + z

x = change(1,2,3)
y = x.a
x.a = y+1
print(x.a)
  1. 3
  2. Error
  3. 7

  • Quelle est la sortie de cet extrait de code :

class test:
     def __init__(self,a):
         self.a=a

     def display(self):
         print(self.a)
obj=test()
obj.display()
  1. Fonctionne normalement, n'affiche rien
  2. Affiche 0, qui est la valeur automatique par défaut
  3. TypeError: __init__() missing 1 required positional argument: 'a'

  • Le code suivant est-il correct ou retournera-t-il une erreur ?

class A:
    def __init__(self,b):
        self.b=b
    def display(self):
        print(self.b)
obj=A("Hello")
del obj
  1. Oui, il est correct
  2. Non, il retournera une erreur

9. Exploitation des classes

Un programme informatique consomme essentiellement deux ressources : du temps de traitement des processeurs, et de l’espace de stockage des données. En machine, l’espace peut être la mémoire vive volatile ou la mémoire de masse persistante.

Il existe trois stratégies d’allocation de la mémoire :

  • l’allocation statique,

  • l’allocation dynamique sur la pile,

  • l’allocation dynamique sur le tas.

L’allocation statique est principalement mise en place dans les langages de programmation compilés (C, C++, Pascal…).

Les langages interprétés (PHP, Python, Perl…) ne peuvent allouer la mémoire que sur demande, lors de l’exécution du script.

À chacune des stratégies correspond une région mémoire du programme, ou segment. Ces segments sont nommés text (statique), stack (pile) et heap (tas).

— wikipedia
https://fr.wikipedia.org/wiki/Allocation_de_m%C3%A9moire

Nous allons nous intéresser ici au mécanisme de pile.

9.1. Qu’est-ce qu’une pile ?

Une pile est une structure développée pour stocker des données d’une manière très spécifique. Imaginez une pile de pièces. Vous n’êtes pas en mesure de placer une pièce ailleurs que sur le dessus de la pile. De même, vous ne pouvez pas retirer une pièce de la pile de n’importe quel endroit autre que le haut de la pile. Si vous voulez obtenir la pièce qui se trouve au fond, vous devez retirer toutes les pièces des niveaux supérieurs.

Le nom alternatif pour une pile (mais uniquement dans la terminologie informatique) est LIFO. C’est une abréviation pour une description très claire du comportement de la pile : Last In - First Out. La pièce qui est arrivée en dernier sur la pile sortira en premier.

Une pile est un objet avec deux opérations élémentaires, appelées conventionnellement push (lorsqu’un nouvel élément est placé en haut) et pop (lorsqu’un élément existant est retiré du haut).

Les piles sont utilisées très souvent dans de nombreux algorithmes classiques, et il est difficile d’imaginer la mise en œuvre de nombreux outils largement utilisés sans l’utilisation de piles.

stack
Figure 5. Mécanisme de stockage de données dans une pile

9.2. L’approche procédurale

Tout d’abord, vous devez décider comment stocker les valeurs qui arriveront sur la pile. La méthode la plus simple et d’utiliser une liste. Supposons que la taille de la pile ne soit en aucune façon limitée et que le dernier élément de la liste stocke l’élément supérieur de la pile.

stack = []

Fonction d’ajout d’un élément sur la pile :

  • Nom de la fonction : push

  • Paramètre : valeur à ajouter sur la pile

  • Retourne : rien

  • La fonction ajoute la valeur à ajouter sur la pile en fin de liste

Fonction de retrait de l’élément du haut de la pile :

  • Nom de la fonction : pop

  • Paramètre : aucun

  • Retourne : la valeur de l’élément retiré de la pile

  • La fonction retire l’élément situé en haut de la pile, c’est à dire le dernier élément de la liste

9.2.1. Travail à faire : Implémentation de la pile

  • Implémenter le code nécessaire au fonctionement de la pile selon l’approche procédurale.

  • Ajouter à la pile la valeur 3, observer le contenu de la pile

  • Ajouter à la pile la valeur 2, observer le contenu de la pile

  • Ajouter à la pile la valeur 1, observer le contenu de la pile

  • Retirer les éléments de la pile un par un et observer le résultat entre chaque retrait

Correction
stack = []

def push(val):
    stack.append(val)


def pop():
    val = stack[-1]
    del stack[-1]
    return val

push(3)
print(stack)
push(2)
print(stack)
push(1)
print(stack)

print(f"On retire {pop()}")
print(stack)
print(f"On retire {pop()}")
print(stack)
print(f"On retire {pop()}")
print(stack)

9.3. L’approche procédurale vs l’approche orientée objet

procedural vs object
Figure 6. Implémentation d’une pile : comparaison des approches

L’approche procédurale fonctionne, la pile est entièrement implémentée, et vous pouvez l’utiliser si vous en avez besoin.

Mais plus vous l’utilisez souvent, plus vous allez rencontrer des inconvénients. Par exemple :

  • la variable essentielle (la liste) est très vulnérable. N’importe qui peut la modifier de manière incontrôlable, détruisant la pile. Ce n’est pas forcément de la malveillance, cela peut se produire à la suite d’une négligence, par exemple, lorsque quelqu’un confond des noms de variables; imaginez que vous avez accidentellement écrit quelque chose comme ceci, le fonctionnement de la pile serait complètement désorganisé :

stack[0] = 0
  • Si vous avez besoin de plus d’une pile, vous devrez créer d’autre listes pour le stockage de ces autre piles, et probablement aussi d’autres fonctions push et pop.

  • Si vous aeez besoin non seulement de fonctions push et pop, mais aussi de certaines autres commodités. Imaginer ce qui se passerait si vous aviez des dizaines de piles implémentées séparément. Il faudrait implémenter des dizaines de fonctions différentes qui font le même travail !

*L’approche objet fournit des solutions pour chacun des problèmes identifiés ci-dessus :

  • L’encapsulation offre la possibilité de cacher (protéger) les valeurs sélectionnées contre tout accès non autorisé. les valeurs encapsulées ne sont ni accessibles ni modifiables si vous souhaitez les utiliser exclusivement.

  • Une classe est une fabrique d’objet. Lorsque vous avez une classe implémentant tous les comportements de pile nécessaires, vous pouvez produire autant d’objets piles que vous le souhaitez. Vous n’avez pas besoin de copier ou de répliquer certaine partie du code.

  • L’héritage offre la possibilité d’enrichir la pile de nouvelles fonctionnalités. Vous pouvez créer une nouvelle classe (une sous-classe) qui hérite de tous les traits existants de la superclasse et en ajoute de nouveaux.

9.4. Approche orientée objet

Nous allons donc créer une classe qui permettra de créer des objets Piles :

class Stack:
    pass
  • Le constructeur doit créer la liste qui contient la pile. Cette liste est protégée et n’est pas accessible directement depuis l’exterieur de la classe.

  • Les actions de retrait et d’ajout d’un élément de la pile sont les méthodes pop et push

  • En plus de ses deux méthodes, on ajoutera :

    • une méthode length qui retourne le nombre d’élément contenu dans la pile

    • une méthode display qui retourne le contenu de la pile

9.5. Travail à faire : Implémentation de la classe Stack

  • Implémenter la classe Stack conformément aux indication données ci-dessus :

  • Créer un objet stack1 puis :

    • Ajouter la valeur 1 dans la pile stack1 et afficher sa longueur et son contenu

    • Ajouter la valeur 2 dans la pile stack1 et afficher sa longueur et son contenu

    • Ajouter la valeur 3 dans la pile stack1 et afficher sa longueur et son contenu

    • Retirer un à un les éléments de la pile stack1 et afficher sa longueur et son contenu à chaque fois

Correction
class Stack:
    def __init__(self):
        self.__stackList = []

    def push(self, val):
        self.__stackList.append(val)

    def pop(self):
        val = self.__stackList[-1]
        del self.__stackList[-1]
        return val

    def length(self):
        return len(self.__stackList)

    def display(self):
        return self.__stackList


stack1 = Stack()

stack1.push(1)
print(f"Stack1 à {stack1.length()} éléments : {stack1.display()}")
stack1.push(2)
print(f"Stack1 à {stack1.length()} éléments : {stack1.display()}")
stack1.push(3)
print(f"Stack1 à {stack1.length()} éléments : {stack1.display()}")

print(f"On enlève le {stack1.pop()}. Stack1 à {stack1.length()} éléments : {stack1.display()}")
print(f"On enlève le {stack1.pop()}. Stack1 à {stack1.length()} éléments : {stack1.display()}")
print(f"On enlève le {stack1.pop()}. Stack1 à {stack1.length()} éléments : {stack1.display()}")

9.6. Création de plusieurs piles

Nous pouvons désormais avoir plus d’une pile se comportant de la même manière. Chaque pile aura sa propre copie de données privées, mais utilisera le même ensemble de méthodes.

9.7. Travail à faire : Manipuler deux piles

  • Créer deux piles à partir de la même classe : stack1 et stack

  • Ajouter la valeur 1 dans la pile stack1 et afficher la longueur et le contenu des deux piles

  • Retirer un élément de la pile stack1 et l’ajouter dans la pile stack2. Afficher la longueur et le contenu des deux piles

Correction
class Stack:
    def __init__(self):
        self.__stackList = []

    def push(self, val):
        self.__stackList.append(val)

    def pop(self):
        val = self.__stackList[-1]
        del self.__stackList[-1]
        return val
    def length(self):
        return len(self.__stackList)

    def display(self):
        return self.__stackList


stack1 = Stack()
stack2 = Stack()

stack1.push(1)
print(f"Stack1 à {stack1.length()} éléments : {stack1.display()}")
print(f"Stack2 à {stack2.length()} éléments : {stack2.display()}")

print(f"On enlève le dernier élément de stack1 et on l'ajoute à stack2")
stack2.push(stack1.pop())

print(f"Stack1 à {stack1.length()} éléments : {stack1.display()}")
print(f"Stack2 à {stack2.length()} éléments : {stack2.display()}")

9.8. Spécialisation

Allons maintenant un peu plus loin. Ajoutons une nouvelle classe pour gérer les piles.

Cette nouvelle classe devrait être en mesure d’évaluer la somme de tous les éléments actuellement stockés sur la pile.

Nous pourrions modifier la classe Stack en lui ajoutant une nouvelle méthode mais il arrive parfois que cette classe soit définie dans une librairie et ne soit donc pas modifiable. Il est aussi possible que cette classe nous satisfasse pleinement et que les nouveaux comportements que nous souhaitons soient limités. Nous allons donc spécialiser la classe Stack, ce qui revient à construire une sous-classe de la classe Stack.

La première étape est simple: il suffit de définir une nouvelle sous-classe pointant vers la classe qui sera utilisée comme super-classe :

class Stack:
    ...

class AddingStack(Stack):
    pass

L’héritage s’obtient en indiquant dans les parenthèse le nom de la super-classe :

La classe AddingStack hérite de la classe Stack

  • Super-classe : Stack

  • Sous-classe : AddingStack

Toutes les instances de AddingStack possèdent les attributs et les méthodes définis dans la super-classe Stack

9.9. Travail à faire : Implémentation de la sous-classe AddingStack

On souhaite :

  • Une méthode get_sum retourne la somme de tous les éléments présents dans la pile

  • La méthode push est redéfinie pour ajouter le nouvel élément à la somme

  • La méthode pop est redéfinie pour retrancher l’élément sorti de la pile

  • Modifier le constructeur :

class AddingStack(Stack):
    def __init__(self):
        Stack.__init__(self)
        self.__sum = 0
  • __sum : attribut privé qui contiendra la somme des éléments de la pile

  • Stack._init_(self) : appel explicite au constructeur de la super-classe pour hériter de toutes ses caractéristiques. Nous possédons désormais un objet de la super-classe Stack qui permet d’accéder à ses attributs et ses méthodes :

Stack.push(val)  #Appel de la méthode push() définie dans la classe Stack
Stack.pop()      #Appel de la méthode pop() définie dans la classe Stack
  • Redéfinir les méthodes push et pop dans la sous-classe AddingStack :

class AddingStack(Stack):
    def __init__(self):
        Stack.__init__(self)
        self.__sum = 0

    def push(self, val):
        pass

    def pop(self, val):
        pass
  • Implémenter la méthode get_sum qui retourne la somme des éléments de la pile :

  • Créer un objet stack1 de la classe AddingStack puis :

    • Ajouter la valeur 10 dans la pile stack1 et afficher sa longueur et son contenu et la somme de ses éléments

    • Ajouter la valeur 20 dans la pile stack1 et afficher sa longueur et son contenu et la somme de ses éléments

    • Ajouter la valeur 30 dans la pile stack1 et afficher sa longueur et son contenu et la somme de ses éléments

    • Retirer un à un les éléments de la pile stack1 et afficher sa longueur, son contenu et la somme de ses éléments à chaque fois

Correction
class Stack:
    def __init__(self):
        self.__stackList = []

    def push(self, val):
        self.__stackList.append(val)

    def pop(self):
        val = self.__stackList[-1]
        del self.__stackList[-1]
        return val

    def length(self):
        return len(self.__stackList)

    def display(self):
        return self.__stackList


class AddingStack(Stack):
    def __init__(self):
        Stack.__init__(self)
        self.__sum = 0

    def get_sum(self):
        return self.__sum

    def push(self, val):
        self.__sum += val
        Stack.push(self, val)

    def pop(self):
        val = Stack.pop(self)
        self.__sum -= val
        return val


stack1 = AddingStack()

stack1.push(10)
print(f"Stack1 à {stack1.length()} éléments : {stack1.display()} / Somme = {stack1.get_sum()}")
stack1.push(20)
print(f"Stack1 à {stack1.length()} éléments : {stack1.display()} / Somme = {stack1.get_sum()}")
stack1.push(30)
print(f"Stack1 à {stack1.length()} éléments : {stack1.display()} / Somme = {stack1.get_sum()}")

print(f"On enlève le {stack1.pop()}. Stack1 à {stack1.length()} éléments : {stack1.display()} / Somme = {stack1.get_sum()}")
print(f"On enlève le {stack1.pop()}. Stack1 à {stack1.length()} éléments : {stack1.display()} / Somme = {stack1.get_sum()}")
print(f"On enlève le {stack1.pop()}. Stack1 à {stack1.length()} éléments : {stack1.display()} / Somme = {stack1.get_sum()}")

10. Conclusion

A l’issue de ce cours, vous devez être familiarisé avec le vocabulaire et les principaux concept de la programmation orienté objet :

  • Classe

  • Objet

  • Attributs ou propriétés

  • Privé/Public

  • Méthodes

  • Constructeur

  • Héritage

  • Super-classe et sous-classe

Je vous invite fortement à faire les exercices suivant :


the_end.jpg