Syntaxe
En programmation structurée, le développeur définit des types :
NomType { nomAttribut1: typeAttribut1, nomAttribut2: typeAttribut2 … }
et des fonctions qui travaillent sur ces types :
Fonction nomFunction1 (nomParam1: NomType, nomParam2 : typeParam2 …);
Fonction nomFunction2 (nomParam1: NomType …): TypeRenvoyé;
Exemple en Python
from types import SimpleNamespace #pour créer des structures
# Définition du type :
Personne = type("Personne {nom: str, prenom: str}: ",(SimpleNamespace,),{})
# Définition des fonctions :
def creer_personne(nom: str, prenom: str) -> Personne:
p = Personne()
p.nom = nom
p.prenom = prenom
return p
def renommer_personne(p: Personne, nom: str, prenom: str) -> None:
p.nom = nom
p.prenom = prenom
def obtenir_nom_complet(p: Personne) -> str:
return p.prenom + " " + p.nom
# Programme principal :
if __name__ == "__main__":
p1 : Personne = creer_personne("MITI", "Tehau")
p2 : Personne = creer_personne("KENHOLL", "Koridwen")
print("Nom complet 1 :", obtenir_nom_complet(p1))
renommer_personne(p1, "TEORIKA", "Mireis")
print("Nom complet 1 (renommée) :", obtenir_nom_complet(p1))
print("Nom complet 2 :", obtenir_nom_complet(p2))
Classe et objet
Le principe fondamental de la POO est d’associer la définition des types structurés de données aux fonctions qui les manipulent dans des composants réutilisables et extensibles dénommés classes. Un objet désigne une variable dont le type est une classe.
Attribut et méthode
Une classe est une description abstraite des données et du comportement des objets, que l’on appelle instances de la classe. C’est une sorte de modèle :
- de la structure statique / des caractéristiques des objets : les attributs (parfois appelés propriétés ou champs) ;
- des comportements dynamiques, c’est-à-dire des traitements / actions / opérations réalisables sur les objets : les méthodes (fonctions ou procédures).
Encapsulation
Il convient de masquer les données des objets de la classe pour éviter les erreurs : le développeur d’une classe à la responsabilité de mettre à disposition des fonctions garantissant l’intégrité des objets. C’est le mécanisme de l’encapsulation.
L’encapsulation permet aussi de modifier l’organisation interne d’une classe (pour ajouter de nouvelles fonctionnalités, corriger des erreurs, améliorer les performances…) sans incidence pour les autres utilisateurs de cette classe, facilitant ainsi la maintenabilité des programmes.
C’est la visibilité des attributs et méthodes qui permet de mettre en œuvre l’encapsulation :
- public (
+) : les méthodes publiques sont accessibles depuis l’extérieur de la classe ; les attributs sont rarement publics ; - privé (
-) ou protégé (#) : les attributs et méthodes privés sont encapsulés donc inaccessibles en dehors des méthodes la classe.
Les utilisateurs d’une classe manipulent les objets de la classe à travers les méthodes publiques de la classe.
La nuance entre privé et protégé sera étudiée ultérieurement (cours sur l’héritage).
Instanciation et constructeur
Au moment de l’instanciation (de la création) d’un nouvel objet de la classe :
- de la mémoire (RAM) est (automatiquement) allouée pour l’objet ;
- le constructeur de la classe est appelé : son rôle est d’initialiser les attributs de l’objet.
Le constructeur peut accepter des paramètres dont les valeurs seront affectés aux attributs de l’objet. En l’absence de paramètre qui lui est associé, un attribut pourra se voir affecter une valeur par défaut.
Manipulation d’objets
Les méthodes manipulent les objets de la classe. le mot clé self (en Python)
ou this (Java, PHP…) fait référence à l’objet manipulé.
Les accesseurs (getters) et mutateurs (setters) sont des méthodes qui permettent respectivement de renvoyer (après éventuel formatage) ou modifier (en s’assurant de l’intégrité de l’objet) la valeur d’un attribut.
La définition d’une classe peut-être faîte en code (Java, C++, PHP…) ou sous la forme d’un diagramme UML de classes. UML est un langage de modélisation agnostique, c’est-à-dire non lié à un langage de programmation particulier.
Par convention :
- le nom d’une classe est écrit en “PascalCase”, avec une majuscule au début de chaque partie du nom de la classe ; exemple : "LigneCommande" ;
- les attributs sont écrits en minuscules ;
- selon le langage, le nom des méthode est écrit en “PascalCase” ou “camelCase”, avec une minuscule au début à chaque partie du nom ; exemple : "getNom”.
Diagramme de classe UML
Ce diagramme est utilisé pour la documentation développeur de la classe.
Diagramme de domaine UML
Ce diagramme est utile pour représenter la structure des données. Les comportements n’y figurent pas.
Un diagramme de domaine est assez similaire en finalité et forme à un MCD ; il y a néanmoins quelques différences :
- il n’y a pas de concept d’attribut identifiant en POO ; l’annotation
<<id>>est utilisée à cet effet sur le diagramme UML ; - il n’y a pas de concept de visibilité / d’encapsulation dans un MCD ;
- les associations entre classes sont représentées par des objets, et non
pas par des clés étrangères comme c’est le cas avec le MLD ;
exemple la classe
Commandeaura un attributclientde type (classe)Clientet non pas un attributidClientde type entier. - les cardinalités sont inversées, et
*est utilisé à la place den(plusieurs).
Diagramme d’API UML
Ce diagramme présente uniquement les attributs et méthodes publiques de la classe, autrement dit l’API. Il est destiné aux développeurs utilisateurs de la classe.
Exemple
Définition d’une classe
classe NomClasse {
public nomAttribut1: typeAttribut1;
privé / protégé nomAttribut2: typeAttribut2;
…
constructeur(valeurAttribut1: typeAttribut1, …) {
this.nomAttribut1 = valeurAttribut1;
…
}
méthode getAttribut1(): TypeAttribut1 { //accesseur
return this.nomAttribut1;
}
méthode setAttribut1(valeurAttribut1: TypeAttribut1) { //mutateur
this.nomAttribut1 = valeurAttribut1;
}
méthode nomMéthode(…) {
…
}
…
}
Utilisation d’une classe
var obj1 = new NomClasse(paramConstruct1, …); //instanciation
afficher(obj1.getAttribut1());
obj1.setAttribut1(NouvelleValeurAttribut1);
- On part de l’objet pour appeler une de ses méthodes (exemple :
getAttribut1). - Une méthode est une fonction (ou procédure si elle ne renvoie rien) : il faut mettre des parenthèses.
- L’objet (ici
obj1) devientthisdans le corps de la méthode.
La référence à l’objet
En programmation structurée, les fonctions qui manipulent les “objets” prennent
en paramètre une référence vers la variable. C’est aussi le cas des méthodes
des langages orientés objets, même si ce paramètre n’apparait pas dans le
profil de la méthode : la plupart des langages utilisent l’identificateur
this (ceci) pour désigner l’objet manipulé par la méthode.
Ainsi pour une méthode :
classe NomClasse {
…
méthode nomMéthode(param1: TypeParam1…) {
ceci.nomAttribut1 = …
…
}
}
il faut s’imaginer que son profil réel est :
classe NomClasse {
…
méthode nomMéthode(ceci: NomClasse, param1: TypeParam1…) {
ceci.nomAttribut1 = …
…
}
}
et que l’appel obj.nomMéthode(param1…) est en réalité nomMéthode(obj, param1…).
Le Java est considéré comme une des références pour la syntaxe orientée objets.
Fichier “Personne.java"
public class Personne {
private String nom;
private String prenom;
public Personne(String nom, String prenom) { //constructeur
this.nom = nom;
this.prenom = prenom;
}
public void renommer(String nom, String prenom) {
this.nom = nom;
this.prenom = prenom;
}
public String obtenirNomComplet() {
return prenom + " " + nom;
}
}
Fichier “Prog.java"
public class Prog {
public static void main(String[] args) {
var p1 = new Personne("MITI", "Tehau");
var p2 = new Personne("KENHOLL", "Koridwen");
p1.renommer("TEORIKA", "Mirei");
System.out.println("Nom complet : " + p1.obtenirNomComplet());
System.out.println("Nom complet : " + p2.obtenirNomComplet());
}
}
Exécuter le programme
- Pour compiler (bytecode) :
javac Personne.java Prog.java - Pour exécuter :
java Prog- exécution dans la JVM du JRE.
Le langage Python n’implémente de véritable mécanisme d’encapsulation : les attributs sont publics. Néanmoins, les conventions permettent de le simuler.
- lorsque le nom d’un attribut commence par
_, l’attribut est protégé ; - lorsque le nom d’un attribut commence par
__, l’attribut est privé ; un mécanisme interne renomme l’attribut_NomClasse__nomAttribut.
De plus, par convention, c’est self (et non pas this) qui désigne l’objet
manipulé, et il doit figurer en premier paramètre des méthodes.
Fichier “personne.py"
class Personne:
_nom: str
_prenom: str
def __init__(self, nom: str, prenom: str): #constructeur
self._nom = nom
self._prenom = prenom
def renommer(self, nom: str, prenom: str):
self._nom = nom
self._prenom = prenom
def obtenirNomComplet(self) -> str:
return self._prenom + " " + self._nom
Fichier “prog.py"
from personne import Personne
if __name__ == "__main__":
p1 = Personne("MITI", "Tehau")
p2 = Personne("KENHOLL", "Koridwen")
p1.renommer("TEORIKA", "Mirei")
print("Nom complet :", p1.obtenirNomComplet())
print("Nom complet :", p2.obtenirNomComplet())
Exécuter le programme
python3 prog.py
Pour expérimenter, remplacer l’identificateur self par this partout dans
le fichier Personne.py ; le programme fonctionne toujours, même si les conventions
ne sont plus respectées…
Fichier “Personne.php"
<?php
class Personne {
protected string $nom;
protected string $prenom;
public function __construct(string $nom, string $prenom) {
$this->nom = $nom;
$this->prenom = $prenom;
}
public function renommer(string $nom, string $prenom): void {
$this->nom = $nom;
$this->prenom = $prenom;
}
public function obtenirNomComplet(): string {
return $this->prenom . " " . $this->nom;
}
}
?>
Fichier “prog.php"
<?php
require_once 'Personne.php';
$p1 = new Personne("MITI", "Tehau");
$p2 = new Personne("KENHOLL", "Koridwen");
$p1->renommer("TEORIKA", "Mirei");
echo "Nom complet : " . $p1->obtenirNomComplet() . "\n";
echo "Nom complet : " . $p2->obtenirNomComplet() . "\n";
Exécuter le programme
php prog.php- ou déployer sur un serveur web et ouvrir dans un navigateur la page “prog.php”