Connexion élèves

Choisir le(s) module(s) à installer :

La Programmation Orientée Objet ( POO )

Jusqu’ici, les programmes que vous avez écrits ont été réalisés en programmation impérative : ils étaient constitués d'instructions qui se succédaient les unes après les autres, dans l'odre où vous les aviez placées dans le code.

Une variante de la programmation impérative, et que vous avez aussi utilisée, est la programmation procédurale : chaque programme a été décomposé en plusieurs fonctions réalisant des tâches simples.

Cependant, dans certaines situations comme celle vu dans le TP "Champagne !", ce type de programmation trouvait ses limites, car on arrivait alors à un code peu lisible, compliqué, non maintenable, et surtout très peu fiable vu le risque d'erreur que la quantité énorme de variables différentes engendrait.

De plus, lorsque plusieurs programmeurs travaillent simultanément sur un gros projet, il est nécessaire de programmer autrement afin d’éviter les conflits entre les fonctions : c'est la Programmation Orientée Objet, ou POO.

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.

De très nombreux langages actuels fournissent les concepts de la POO, mais n'obligent cependant pas à l'utiliser; c'est le cas de Python par exemple.

La POO n'est pas un nouveau "langage" de programmation à apprendre; c'est une autre façon d'aborder la conception d'un programme et de le coder, on parle d'un paradigme de programmation ( comme l'est aussi la programmation impérative ).

Attributs et méthodes

De manière très générale, un objet se caractérise par 3 choses :

  1. son état
  2. son comportement
  3. son identité

Un objet en informatique est un nouveau type de données, mais qui, au contraire des types "natifs" des langages qui ne stockent que des données, fournit également des instructions.

  • les attributs d'un objet sont des variables qui contiendront les données correspondant à l'état de l'objet.
  • les méthodes d'un objet sont des fonctions qui régissent le comportement de l'objet.
  • le nom d'un objet sera celui d'une variable qui permettra d'identifier l'objet une fois créé.

Notion de classe

En programmation orientée objet, on fabrique de nouveau types de données correspondant aux besoin du programme. On réfléchit alors aux caractéristiques des objets qui seront de ce type et aux actions possibles à partir de ces objets.

Ces caractéristiques et ces actions sont regroupées dans un code spécifique associé au type de données, appelé classe.

Définition

Une classe est un modèle, un "moule" permettant de créer des objets.

Elle comporte l'ensemble des instructions qui définiront et manipuleront les attributs ( désigné avec la syntaxe objet.attribut ) et les méthodes ( syntaxe objet.méthode() ) associés aux objets que l'on créera à partir de la classe.

Une classe a un nom ( qui, par convention, commence par une majuscule ) qui permet de nommer le type des objets défini par la classe ( on dira d'ailleurs : "un objet de type Classe" ).

En Python, une classe se définit avec le mot-clé class :


class MaClasse :

	.............			
			

Codage des méthodes

A l'intérieur de la classe, les méthodes sont définies avec le mot-clé def:


class MaClasse :

	def methode1(self, param1, param2):
		resultat = param1 + param2
		return resultat
			

À première vue une méthode n’est donc qu’une fonction mais :

  • elle peut avoir plusieurs paramètres, mais son premier paramètre représente obligatoirement l’objet à partir duquel elle est appelée
  • par convention, ce premier paramètre est appelé self.

"self" est donc l'équivalent de : "le-nom-de-l'objet-à-partir-duquel-la-méthode-a-été-appelée".

Il est de plus tout à fait possible de passer un objet comme argument à une méthode d'un autre objet, tout à fait comme on peut le faire avec des entiers, des listes, etc...

Il faut alors dans la définition de la méthode appelée, prévoir un paramètre pour cela.

Une méthode particulière : le constructeur

Rôle

En Python, une classe n'a pas l'obligation de déclarer explicitement ses attributs : on peut très bien le faire seulement après avoir créé un objet à partir de la classe ( oui, un objet en Python est a priori créé sans aucun attribut...)

Pour être cependant cohérents avec les autres langages objets qui ne donnent pas cette possibilité ( Python est d'ailleurs le seul...), nous initialiserons toujours les attributs d'un objet lors de sa création.

On utilise pour cela une méthode "spéciale", la méthode __init__(), qui est "automatiquement" appelée lors de la création des objets ( c'est l'équivalent du constructeur des langages sus-nommés ).


class MaClasse :

	def __init__(self): # deux caractères "underscore" de part et d'autre du nom 
		self.attribut1 = 100
		self.attribut2 = None	
	
	def methode1(self, param1, param2):
		resultat = param1 + param2
		return resultat
			

On remarque là-aussi pour les attributs l'utilisation de self qui a ici la signification : "le-nom-de-l'objet-qui-a-cet-attribut".

Les attributs dont le nom est préfixé par self appartiennent en propre aux objets : ils seront différents pour chaque objet que l'on créera à partir de la classe.

Passage d'arguments au constructeur

On peut passer ( en plus de self bien entendu ) d'autres arguments au constructeur d'une classe, arguments qui seront fournis par le programme principal lors de la création d'un objet :


class MaClasse :

	def __init__(self, valeur1, valeur2):
		self.attribut1 = valeur1
		self.attribut2 = valeur2		
	
	def methode1(self, param1, param2):
		resultat = param1 + param2
		return resultat
			

Comme pour toute fonction, ces arguments supplémentaires peuvent prendre des valeurs par défaut, valeurs qui seront associées aux attributs d'un objet lors de sa création sans qu'il soit nécessaire de les fournir alors ( voir plus bas ) :


class MaClasse :

	def __init__(self, valeur1 = 100, valeur2 = None):  # valeurs par défaut
		self.attribut1 = valeur1
		self.attribut2 = valeur2		
	
	def methode1(self, param1, param2):
		resultat = param1 + param2
		return resultat
			

Ces valeurs par défaut peuvent toujours être "écrasées" au cas où l'on en passe des différentes en argument.

Attention à ne jamais utiliser de valeurs par défaut mutables (tableaux, dictionnaires), comme pour toute fonction.

Attributs et variables

Certaines variables dans la définition d'une classe peuvent bien entendu ne pas être des attributs : une méthode peut très bien avoir besoin d'une variable "générique" pour réaliser son traitement, et dans ce cas, il est bien entendu inutile de faire précéder son nom de self.

C'est par exemple le cas du tableau couleurs dans le TP "Champagne !".


class MaClasse :
			
	def __init__(self):
		self.attribut1 = 100
		self.attribut2 = None	
	
	def methode1(self, param1, param2):
		resultat = param1 + param2 # 'resultat' n'est pas un attribut 
		return resultat
			

Instanciation d'un objet

La création d'un objet à partir d'une classe s’appelle l'instanciation de cet objet.

L'objet est alors appelé une instance de la classe.

Pour cela, un appel de la classe (comme si c’était une fonction) depuis le programme principal ( donc, en dehors de la définition de la classe ) crée et renvoie un objet qui est une instance de cette classe; cet objet peut être alors référencé par un nom de variable qui permettra de le désigner dans la suite du programme :


# DÉFINITION DE LA CLASSE
class MaClasse :

	def __init__(self):
		self.attribut1 = 100
		self.attribut2 = None		
	
	def methode1(self, param1, param2):
		resultat = param1 + param2
		return resultat
		
# PROGRAMME PRINCIPAL
objet = MaClasse() # BIEN NOTER LES PARENTHÈSES : la classe est utilisée comme une fonction !
			

Ne pas confondre :

  • le nom de la classe ( ici MaClasse )
  • le nom de l'instance de la classe ( = de l'objet ) ( ici objet ).

Si le constructeur attend des paramètres, il faut bien entendu les lui passer :


# DEFINITION DE LA CLASSE
class MaClasse :

	def __init__(self, valeur1, valeur2):
		self.attribut1 = valeur1
		self.attribut2 = valeur2	
	
	def methode1(self, param1, param2):
		resultat = param1 + param2
		return resultat
		
# PROGRAMME PRINCIPAL
objet = MaClasse(100, None) 
			

Sauf bien sûr, si ces paramètres ont des valeurs par défaut :


# DEFINITION DE LA CLASSE
class MaClasse :

	def __init__(self, valeur1 = 100, valeur2 = None):
		self.attribut1 = valeur1
		self.attribut2 = valeur2	
	
	def methode1(self, param1, param2):
		resultat = param1 + param2
		return resultat
		
# PROGRAMME PRINCIPAL
objet = MaClasse() # Pas d'arguments nécessaires... 
			

Manipulation des objets

Appel des méthodes d'un objet

Comme indiqué plus haut, une méthode doit être accédée comme un attribut ( objet.methode() ), et l’objet est passé implicitement ( c'est à dire sans qu'il soit nécessaire de le préciser ) comme premier argument ( et son nom remplace alors le "self" de la classe ); si la méthode attend d'autres paramètres, il faut bien entendu les lui passer aussi :


# DEFINITION DE LA CLASSE
class MaClasse :

	def __init__(self):
		self.attribut1 = 100
		self.attribut2 = None		
	
	def methode1(self, param1, param2):
		resultat = param1 + param2
		return resultat
		
# PROGRAMME PRINCIPAL
objet = MaClasse() 

objet.methode1(15, 5) # Là aussi, les parenthèses : c'est une fonction !
			

On pourrait faire de même pour accèder directement depuis le programme principal aux attributs de l'objet :


# DEFINITION DE LA CLASSE
class MaClasse :

	def __init__(self):
		self.attribut1 = 100
		self.attribut2 = None		
	
	def methode1(self):
		resultat = self.attribut1 + 1
		return resultat
		
# PROGRAMME PRINCIPAL
objet = MaClasse() 

objet.methode1() 

objet.attribut1 += 20
print(objet.attribut2)
			

Sauf que....

Visibilité des attributs

....ça ne se fait pas !

Attributs publics/privés - Encapsulation

En POO, le principe est que les attributs d'un objet lui sont privés, c'est à dire "visibles" uniquement par lui et donc inaccessibles "de l'extérieur", c'est à dire le programmeur qui a instancié un objet de cette classe. ( = non publics ) : c'est le principe d'encapsulation des données.

Le but de l’encapsulation est de cacher la représentation interne des classes, qui apparaissent alors comme des "boîtes noires" au programmeur, qui n'a besoin de connaître que leur interface, c'est à dire l'ensemble des méthodes servant à manipuler les objets ( nous reviendrons plus tard sur cette notion d'interface ).

Intérêts de l'encapsulation :

Rendre privés des attributs en Python

La notion d'attributs publics/privés n'existe pas par défaut en Python : tout est public ! ( ce qui est souvent un reproche qu'on fait à ce langage quand on l'utilise pour de la POO...)

Il est possible tout de même de rendre privés certains attributs, mais, dans un souci de simplicité, nous ne l'utiliserons pas : nous nous abstiendrons cependant toujours de modifier ou d'accèder à un attribut d'un objet en dehors de la classe.

Conclusion

Python est beaucoup plus "souple" au niveau des concepts de la POO que les autres langages : les attributs privés n'en sont pas vraiment, on peut rajouter des attributs "à la volée" à un objet, etc...

Par contre, utiliser la POO avec rigueur est indispensable dans d'autres langages, comme Java par exemple ( si un jour vous programmez une application Android, vous verrez ce que cela signifie...).

Il y a encore bien d'autres concepts autour de la POO : l'héritage, le polymorphisme,.... mais ils ne sont pas au programme de la Terminale NSI. Si vous voulez en savoir plus, n'éhsitez pas à consulter des sites dédiés à la programmation.

Enfin, ne pensez pas que la POO est obligatoire ou indispensable; on peut très bien s'en passer ( ce que vous avez d'ailleurs fait jusqu'à maintenant ); mais vous verrez cette année à travers différents exemples que c'est souvent très pratique !

QCM de validation