La programmation orienté objet

Nous allons maintenant nous attacher à étudier un paradigme de la programmation : le paradigme objet. Mais qu'est ce qu'un paradigme ? D'après Wikipedia, un paradigme de programmation est une façon d'approcher la programmation informatique et de traiter les solutions aux problèmes et leur formulation dans un langage de programmation approprié.

Dit autrement, c'est la façon dont un programmeur perçoit les données utilisées par son programme et les outils qu'il utilisera pour traiter ces données. Sa vision du monde informatique en quelque sorte.

Ce paradigme de programmation remonte aux années 60, avec les langages simula et smalltalk. Il a ensuite infusé dans l'essentiel des langages de programmation. Dans la programmation telle que nous l'avons vu jusqu'ici, nous avons des variables d'un côté (les données) et nous réalisons des actions qui manipulent ces données (les blocs d'instructions, les procédures, les fonctions). L'idée en programmation objet est d'avoir des structures informatiques que l'on appellera objet qui regrouperont à la fois les données sur cet objet et les actions possibles avec cet objet

Prenons l'exemple très courant d'une voiture. Une voiture aura les caractéristiques suivantes :

On peut aussi dire qu'avec une voiture, on peut effectuer les actions suivantes :

Toutes les voitures ont ces éléments en commun. On dit que l'on a une classe d'objets qui est défini par cet ensemble de caractéristiques et d'actions. La classe voiture est un type abstrait qui représente ce que les voitures ont en commun.

Lorsque l'on parle d'un véhicule en particulier, on dit que l'on parle d'une instance de la classe, que l'on appelle un objet. Par exemple, la voiture de votre tante Michu est une instance de la classe Voiture. C'est du "concret", là où la classe décrit de façon abstraite

En langage de programmation orienté objet, les caractéristiques des objets d'une classe sont appelés les attributs ou les propriétés (le terme attribut est le plus fréquent). Les fonctions qui s'appliquent aux objets sont appelés des méthodes.

Python et la programmation objet

En théorie, la programmation orientée objet (POO) est une façon de concevoir les programmes qui pourrait être utilisée avec n'importe quel langage évolué de programmation. En pratique, certains langages sont plus adaptés que d'autres. À la base de sa conception, Python est un langage orienté objet, et en Python, presque tout est objet. La POO est donc une méthode parfaitement naturelle pour progammer en Python. Le langage s'y prête parfaitement. Nous allons étudier cette façon de faire grâce à un combat entre Gollum et Bilbo, inspiré de Bilbo le Hobbit (et d'après l'excellent travail de David Roche sur Pixees)

Notre première classe

La création d'une classe en python commence toujours par le mot class. Ensuite toutes les instructions de la classe seront indentées :

class LeNomDeMaClasse:
    #instructions de la classe
#La définition de la classe est terminée.

La classe est une espèce de moule (nous reviendrons plus tard sur cette analogie qui a ses limites), à partir de ce moule nous allons créer des objets (plus exactement nous parlerons d'instances). Par exemple, nous pouvons créer une classe voiture, puis créer différentes instances de cette classe (Peugeot407, Renault Espace,...). Pour créer une de ces instances, la procédure est relativement simple :

peugeot407 = Voiture()

Cette ligne veut tout simplement dire : "crée un objet (une instance) de la classe Voiture que l'on nommera peugeot407." Ensuite, rien ne nous empêche de créer une deuxième instance de la classe Voiture :

renaultEspace = Voiture()

Nous rencontrons ici la limite de notre analogie avec le moule. En effet 2 objets fabriqués avec le même moule seront (définitivement) identiques, alors qu'ici nos 2 instances pourront évoluer différemment. Pour développer toutes ces notions (et d'autres), nous allons écrire un premier programme : Nous allons commencer par écrire une classe Personnage (qui sera dans un premier temps une coquille vide) et, à partir de cette classe créer 2 instances : bilbo et gollum.

Analysez puis testez le code suivant (pour rappel, pass ne fait rien)
class Personnage:
  pass

gollum = Personnage()
bilbo = Personnage()

Pour l'instant, notre classe ne sert à rien et nos instances d'objet ne peuvent rien faire. Comme il n'est pas possible de créer une classe totalement vide, nous avons utilisé l'instruction pass qui ne fait rien. Ensuite nous avons créé 2 instances de la classe Personnage : gollum et bilbo. Comme expliqué précédemment, une instance de classe possède des attributs et des méthodes. Commençons par les attributs : Un attribut possède une valeur (un peu comme une variable). Nous allons associer un attribut vie à notre classe Personnage (chaque instance aura un attribut vie, quand la valeur de vie deviendra nulle, le personnage sera mort !) Ces attributs s'utilisent comme des variables, l'attribut vie pour bilbo sera noté

bilbo.vie

de la même façon l'attribut vie de l'instance gollum sera noté

gollum.vie
Analysez le code suivant :
class Personnage:
    pass

gollum=Personnage()
gollum.vie=20
bilbo=Personnage()
bilbo.vie=20

Comme pour une variable il est possible d'utiliser la console Python pour afficher la valeur référencée par un attribut. Il suffit de taper dans la console gollum.vie ou bilbo.vie (sans bien sûr avoir oublié d'exécuter le programme au préalable.) Cette façon de faire n'est pas très "propre" et n'est pas une bonne pratique En effet, nous ne respectons pas un principe de base de la POO : l'encapsulation Il ne faut pas oublier que notre classe doit être "enfermée dans une caisse" pour que l'utilisateur puisse l'utiliser facilement sans se préoccuper de ce qui se passe à l'intérieur, or, ici, ce n'est pas vraiment le cas. En effet, les attributs (gollum.vie et bilbo.vie), font partie de la classe et devraient donc être enfermés dans la "caisse" ! Pour résoudre ce problème, nous allons définir les attributs, dans la classe, à l'aide d'une méthode (une méthode est une fonction définie dans une classe) d'initialisation des attributs. Cette méthode est définie dans le code source par la ligne :

def __init__ (self)

La méthode __init__ est automatiquement exécutée au moment de la création d'une instance. Le mot self est obligatoirement le premier argument d'une méthode (nous reviendrons ci-dessous sur ce mot self) Nous retrouvons ce mot self lors de la définition des attributs. La définition des attributs sera de la forme :

self.vie=20
Le mot self représente l'instance. Quand vous définissez une instance de classe (bilbo ou gollum) le nom de votre instance va remplacer le mot self. Dans le code source, nous allons avoir :
class Personnage:
def __init__ (self):
    self.vie=20
Ensuite lors de la création de l'instance gollum, python va automatiquement remplacer self par gollum et ainsi créer un attribut gollum.vie qui aura pour valeur de départ la valeur donnée à self.vie dans la méthode __init__ Il se passera exactement la même chose au moment de la création de l'instance bilbo, on aura automatiquement la création de l'attribut bilbo.vie. Analysez le code ci dessous. Vous pouvez vous aider de son éxécution sur Python Tutor
class Personnage:
    def __init__(self):
        self.vie=20
gollum=Personnage()
bilbo=Personnage()

Nous avons toujours deux personnages qui ont chacun 20 points de vie. Mais cette fois nous n'avons pas défini l'attribut gollum.vie=20 et bilbo.vie=20 en dehors de la classe, nous avons utilisé une méthode __init__. Imaginons que nos 2 personnages n'aient pas au départ les mêmes points de vie ! Pour l'instant, impossible d'introduire cette contrainte (self.vie=20) Une méthode, comme une fonction, peut prendre des paramètres. Le passage de paramètres se fait au moment de la création de l'instance, comme dans le code ci dessous, que vous allez tester.

  • affichez le nombre de point de vie de gollum et celui de bilbo (en utilisant par exemple print(gollum.vie))
  • diminuez de 5 le nombre de point de vie de Gollum. Bilbo se repose, prend un peu de boisson revigorante et en regagne 3.
  • Affichez ces nouveaux points de vie
class Personnage: def __init__(self,nbreDeVie): self.vie=nbreDeVie gollum=Personnage(15) bilbo=Personnage(20) # Ajoutez votre code ci dessous

Au moment de la création de l'instance gollum, on passe comme argument le nombre de vies (gollum=Personnage (20)). Ce nombre de vies est attribué au premier argument de la méthode __init__ , la variable nbreDeVie (nbreDeVie n'est pas tout à fait le premier argument de la méthode __init__ puisque devant il y a self, mais bon, self étant obligatoire, nous pouvons dire que nbreDeVie est le premier argument non obligatoire).

N.B. Je parle bien de variable pour nbreDeVie (car ce n'est pas un attribut de la classe personnage puisqu'elle ne commence pas par self).

Nous pouvons passer plusieurs arguments à la méthode __init__ (comme pour n'importe quelle fonction). Nous allons créer 2 nouvelles méthodes :

Ajouter au fur et à mesure les lignes suivantes et testez à chaque fois le programme
  • gollum.donneEtat()
  • bilbo.donneEtat()
  • gollum.perdVie()
  • gollum.donneEtat()
  • bilbo.perdVie()
  • bilbo.donneEtat()
class Personnage: def __init__(self, nbreDeVie): self.vie=nbreDeVie def donneEtat (self): return self.vie def perdVie (self): self.vie=self.vie-1 gollum = Personnage(20) bilbo = Personnage(15)

Vous avez sans doute remarqué que lors de "l'utilisation" des instances biblo et gollum, nous avons uniquement utilisé des méthodes et nous n'avons plus directement utilisé des attributs (plus de "gollum.vie"). Il est important de savoir qu'en dehors de la classe l'utilisation des attributs est une mauvaise pratique en programmation orientée objet : les attributs doivent rester "à l'intérieur" de la classe, l'utilisateur de la classe ne doit pas les utiliser directement. Il peut les manipuler, mais uniquement par l'intermédiaire d'une méthode (la méthode self.perdVie() permet de manipuler l'attribut self.vie)

Nos personnages peuvent boire une potion qui leur ajoute un point de vie.

Modifiez le programme ci-dessous en ajoutant une méthode boirePotion. Testez ensuite cette modification en l'utilisant dans votre programme

class Personnage: def __init__(self, nbreDeVie): self.vie=nbreDeVie def donneEtat (self): return self.vie def perdVie (self): self.vie=self.vie-1 gollum = Personnage(20) bilbo = Personnage(15) gollum.donneEtat() bilbo.donneEtat() gollum.perdVie() gollum.donneEtat() bilbo.perdVie() bilbo.donneEtat()

Selon le type d'attaque subit, le personnage peut perdre plus ou moins de points de vie. Pour tenir compte de cet élément, il est possible d'ajouter un paramètre à la méthode perdVie :

Analysez et testez le code ci dessous
class Personnage: def __init__(self, nbreDeVie): self.vie=nbreDeVie def donneEtat (self): return self.vie def perdVie (self,nbPoint): self.vie=self.vie-nbPoint bilbo = Personnage(15) bilbo.perdVie(2) point=bilbo.donneEtat() print(point)

Il est possible d'ajouter une part d'aléatoire dans la méthode perdVie. Voici une proposition de code :

N.B : random.random() renvoie une valeur aléatoire comprise entre 0 et 1

Nous allons maintenant organiser un combat virtuel entre nos 2 personnages.

  • Analysez ce programme
  • Éxécutez le plusieurs fois pour vérfier
optionnel : si vous le souhaitez, vous pouvez modifier le programme ci dessus pour avoir la possibilité (aléatoire) qu'un personnage, au lieu d'attaquer, boive une potion et regagne un ou deux nombre de vies.

Et nos algoblobs ?

Nous allons définir nos algoblobs en utilisant ce paradigme objet, ce qui sera beaucoup plus propre à terme. Voici les attributs dont seront dotés nos algoblobs :

Les méthodes qui s'appliquent à un blob, outre l'initialisation, sont limitées pour l'instant :

Reprenez votre dernier programme sur les algoblobs et créer une classe algoblob que vous utiliserez à la place des dictionnaires.

optionnel : vous pouvez ajouter des propriétés et des méthodes à votre classe, pour gérer par exemple un rayon qui évolue, la couleur, la téléportation sur demande... soyez imaginatif !