Encapsulation


Dans un programme contenant la définition d'une classe, on peut distinguer le code se trouvant à l'intérieur de la classe (donc dans le corps de ses méthodes) et celui qui se trouve à l'extérieur (instructions ne figurant dans aucune méthode de la classe).

L'encapsulation est une manière de définir une classe de telle sorte que ses attributs ne puisse pas être directement manipulés depuis l'extérieur, mais seulement indirectement par l'intermédiaire de ses méthodes.

Un des avantages de cette approche est la possiblité de redéfinir la représentation interne des attributs, sans que cela affecte la manipulation externe d'un objet de cette classe.

L'encapsulation facilite donc la mise à jour des applications. On peut en voir l'intéret dans un projet de développement réalisé par plusieurs développeurs (ce qui est généralement le cas en entreprise). Chaque développeur est responsable d'une partie du projet. Si les classes dont il est le responsable sont proprement encapsulées, il pourra modifier par la suite leurs représentations internes, sans que cela perturbe le travail des autres développeurs du projet susceptibles d'utiliser ces classes.

Visibilité des membres d'une classe

Un membre d'une classe désigne un attribut ou une méthode définie dans cette classe.

La visibilité des membres d'une classe définit les endroits d'où ils peuvent être utilisés. Dans cette partie du cours, nous allons voir deux sortes de visibilité: la visibilité publique et la visibilité privée.

Les membres privés d'une classe ne sont accessibles que par les méthodes de la classe:

Les membres publics d'une classe sont accessibles de l'intérieur et de l'extérieur de la classe, par conséquent:

Une classe est encapsulée, si tous ses attributs sont privés.

Les principes que nous venons d'énoncé sont valables dans tous les langages orientés objet. Voyons à présent à travers un exemple, comment définir une classe encapsulée en Python.

Visibilité des membres en Python

En Python, un membre est privé si son nom débute et ne se termine pas par deux caractères soulignés. Dans le cas contraire il est publique.

Exemple: __Compteur est un attribut privé, alors que Compteur est publique. Le constructeur de toute classe __init__ est publique car il se termine par deux caractères soulignés.

Exemple de classe encapsulée en Python

L'exemple de projet présenté ici se trouve dans Exemple-Python-ProgObjet1/Voiture3.

Le fonctionnement du programme est légèrement différent de celui de Voiture2. L'affichage des propriétés de la voiture se fait par un choix supplémentaire de la boucle de saisie:

Voiture3.jpg, 16kB

D'autre part les choix Nouvelle voiture et Rouler ne provoquent pas l'affichage des propriétés de la voiture.

Exemple d'exécution
Voiture3-Exe.jpg, 56kB

Voici le contenu de la nouvelle classe Voiture:

class Voiture :
	
  def __init__ (self, im)  :
      self.__Compteur = 0
      self.__Immat = im
	
  def ChangerPneu (self) :
      return (self.__Compteur >= 10000)    
	
  def getCompteur (self) :
      return self.__Compteur
	
  def setCompteur (self, km) :
      self.__Compteur = km
		
  def getImmat (self) :
      return self.__Immat
	
  def setImmat (self,im) :
      self.__Immat = im

Vous constarez que:

  1. Les attributs __Immat et __Compteur sont à présent privés, car leurs noms commencent par __. La classe est donc encapsulée.
  2. Quatre nouvelles méthodes ont été rajoutées: getCompteur, setCompteur, getImmat et setImmat. Ces méthodes puliques sont appelées des accesseurs car elles permettent d'accéder indirectement aux attributs privés depuis l'extérieur de la classe.
  3. Les méthodes Rouler et AfficherAttributs ont été supprimées, afin de pouvoir mieux illustrer le principe des accesseurs.
Principe des accesseurs

L'encapsulation d'une classe interdit d'accéder à ses attributs depuis l'extérieur, mais elle autorise d'y accéder indirectement via des méthodes. Les méthodes jouant ce rôle sont appelées des accesseurs. On distingue les accesseurs en lecture des accesseurs en écriture:

Utilisation de la classe Voiture

Voyons à présent comment la classe Voiture peut être utilisée.

La procédure associée au choix Rouler a été modifiée pour prendre en compte l'encapsulation de la classe Voiture :

def Choix_Rouler () :
  d = int(input("Distance : "))
  if not LaVoiture.ChangerPneu() :
      LaVoiture.setCompteur( LaVoiture.getCompteur() + d ) 
	else :
		print("Vos pneus sont uses !")

Etant donnée que la méthode Rouler n'existe plus, nous somme obligé d'augmenter le compteur en utilisant les accesseurs de l'attribut Compteur.

Et voici le code de la procédure associée au nouveau choix Afficher:

def Choix_Afficher () :
  print("\n\tLa voiture : ",end="")
  print("Immatriculation: ",LaVoiture.getImmat(),end="") 
  print(", Kilometrage: ",LaVoiture.getCompteur()) 

Etant donné que la méthode AfficherAttributs n'existe plus, nous somme obligés d'utiliser les accesseurs getImmat et getCompteur pour accéder aux valeurs des attributs __Immat et __Compteur.

On y gagne quoi ?

Arrivé à ce point, le lecteur pourra se demander ce que l'on a gagné avec l'encapsulation. Au premier abord, cela parait plus compliqué puisque l'on ne peut plus accéder aux attributs librement. On a l'impression de s'imposer inutilement des contraintes.

L'intéret de l'encapsulation est essentiellement dans la facilité de mise à jour d'un logiciel. Dans notre exemple, la classe Voiture est à présent facilement réutilisable dans d'autres contextes.

Nous pouvons à présent modifier la représentation des données de la classe sans que cela affecte son utilisation. On pourrait par exemple ajouter un nouvel attribut privé: un booléen indiquant si les pneus sont usé. Tant que l'utilisation des méthodes (publiques forcément) de la classe Voiture est inchangée, cette modification sera totalement transparente à l'utilisateur de la classe.

On peut également mettre en doute l'intéret des accesseurs, surtout lorsqu'il s'agit simplement de lire ou de modifier la valeur d'un attribut. N'est-il pas plus simple dans ce cas d'accéder directement à cet attribut ? Oui, mais l'accès aux attributs par des accesseurs vous donne certains avantages: