Les fondements de la programmation orientée objet remontent aux années 1960, avec les travaux de Ole-Johan Dahl (photo) et Kristen Nygaard. Ce n’est néanmoins que dans les années 1970 qu’un véritable langage objet opérationnel avec la proposition de Smalltalk par Alan Kay.
Les grands principes de l’approche objet ont été formalisés au cours du temps, pour devenir aujourd’hui un paradigme mature.
Un objet est une représentation de quelque chose (nous verrons plus tard qu’il s’agit en fait d’un modèle).
Quelque chose, c’est à dire à peu près tout ce que l’on veut : une maison, un micro-processeur, une personne ou encore une instruction en langage de programmation.
En termes plus informatiques, un objet est une structure de données (comme une chaîne de caractères, un nombre entier…).
Sa particularité est qu’il représente à la fois des données (par exemple le nom d’une personne, la puissance d’un micro-processeur…) et les traitements qu’il est possible de réaliser sur ces données (réaliser un calcul pour un micro-processeur, ouvrir la porte pour une maison…). On parle d’attributs pour les données et de méthodes pour les traitements.
Tous les langages objets ne permettent pas de créer et manipuler des classes, contrairement à ce que beaucoup de gens pensent !
Il existe deux types de langages objet :
Une classe est un « moule » à objets, la description d’un ensemble d’objets par leur caractéristiques. Par exemple une classe Personne
pourrait décrire le fait que tous les objets de type Personne
ont un nom, un prénom et une date de naissance : autant d’attributs. Elle pourrait également spécifier qu’une personne peut dormir, se réveiller ou encore marcher : les méthodes de la classe.
Des langages objet à classes sont par exemple Java, C++, Python, Ruby ou encore PHP.
Un exemple en Java :
// Personne.java
public class Personne {
private String nom;
private String prenom;
public Personne(String prenom, String nom) {
this.prenom = prenom;
this.nom = nom;
}
public void direBonjour() {
System.out.println("Bonjour, je m'appelle " + this.prenom + " " + this.nom);
}
}
// MonProgramme.java
public class MonProgramme {
public static void main(String[] args) {
Personne pascal = new Personne("Pascal", "Lando");
pascal.direBonjour();
}
}
Un prototype est un objet à partir duquel on crée de nouveaux objets. C’est une sorte d’exemplaire modèle d’une famille d’objets.
Des exemple de langages objet à prototypes sont : Lua, Javascript, Self, Lisaac ou encore ActionScript.
Un exemple en Lua :
-- On commence par définir un objet, qui servira de prototype
Personne = {
nom = "Inconnu"
}
-- On définit ensuite une sorte de constructeur, qui permettra de dupliquer
-- ce prototype pour en créer de nouveaux
function Personne.copier(o)
setmetatable(o, { __index = Personne })
return o
end
-- On peut maintenant utiliser notre constructeur pour créer des objets qui
-- ont les mêmes caractéristiques que notre prototype...
local pascal = Personne.copier({prenom="Pascal", nom="Lando"})
local simon = Personne.copier({prenom="Simon"})
-- On peut également, au besoin, ajouter des méthodes à notre prototype...
function Personne:direBonjour()
print("Bonjour, je m'appelle " .. self.prenom .. " " .. self.nom .. " ")
end
-- Tous les objets qui ont été fabriqués à partir du prototype disposeront
-- alors de ces méthodes !
pascal:direBonjour() -- Affiche "Bonjour, je m'appelle Pascal Lando"
simon:direBonjour() -- Affiche "Bonjour, je m'appelle Simon Inconnu"
-- Chaque objet vit sa vie indépendamment des autres. On peut par exemple
-- modifier la méthode `direBonjour()` de l'objet `pascal` :
function pascal:direBonjour()
print("Bonjour, je m'appelle " .. self.prenom .. " " .. self.nom .. ", et je suis un formateur !")
end
-- Cette modication n'aura d'effet que sur pascal, et pas sur simon ni sur
-- d'éventuels nouveaux objets créés à partir du prototype initial !
pascal:direBonjour() -- Affiche "Bonjour, je m'appelle Pascal Lando, et je suis un formateur !"
simon:direBonjour() -- Affiche "Bonjour, je m'appelle Simon Inconnu"
local ginette = Personne.copier({prenom="Ginette", nom="Durand"})
ginette:direBonjour() -- Affiche "Bonjour, je m'appelle Ginette Durand"
L’héritage est l’un des concepts phares du paradigme objet.
Il permet de définir des classes d’objets à partir de classes existantes, en en étendant les caractéristiques (propriétés et méthodes) : c’est la fameuse relation est un, dont nous parlerons en détail dans la section consacrée aux diagrammes de classes.
Voici un exemple illustrant l’héritage, en Java :
// Personne.java
public class Personne {
private String nom;
private String prenom;
}
// Etudiant.java
public class Etudiant extends Personne {
private String numeroEtudiant;
}
Dans cet exemple, la classe Etudiant
étend la classe Personne
: elle en hérite les attributs et méthodes (nom
et prenom
) et en définit, en plus, de nouvelles (numeroEtudiant
).
L’héritage est donc un mécanisme très puissant et pratique pour rendre du code modulaire et extensible.
Le terme « polymorphisme » signifie qu’un élément peut prendre des formes différentes, overriding et overloading : sexy, non ? Ces noms bizarres expriment des choses assez simples en fait, nous allons le voir avec des exemples.
L’overriding (on dit aussi surcharge, surdéfinition ou polymorphisme ad-hoc) consiste en l’utilisation d’un même nom de méthode, pouvant être appliqué à plusieurs types d’objets différents issus d’une même arborescence de classes.
Voici un exemple d’utilisation du polymorphisme en Python.
from math import pi
class Figure(object):
def calculerPerimetre(self):
raise NotImplementedError
class Carre(Figure):
def __init__(self, cote):
self.cote = cote
def calculerPerimetre(self):
return 4 * self.cote
class Cercle(Figure):
def __init__(self, rayon, nom="Anonyme"):
self.rayon = rayon
def calculerPerimetre(self):
return 2 * pi * self.rayon
Ici la méthode calculerPerimetre
est définie pour les classes Carre
et Cercle
, toutes deux issues d’une même classe mère, avec des fonctionnalités différentes.
L’overloading consiste à définir plusieurs fois, dans même une classe, une méthode ou un opérateur. La méthode ou l’opérateur en question peuvent alors être utilisés dans des contextes différents.
Voici un exemple de surcharge d’opérateur en Python (l’opérateur « inférieur à », « less than » en anglais, autrement dit lt
.
from math import pi
class Figure(object):
def calculerPerimetre(self):
raise NotImplementedError
class Carre(Figure):
def __init__(self, cote):
self.cote = cote
def __lt__(self, autre):
return self.cote < autre.cote
mon_carre = Carre(10)
mon_autre_carre = Carre(8)
print(mon_autre_carre < mon_carre)
True
Ici, nous avons définit le fait que l’opérateur <
permet de « comparer des carrés » sur la base de leur longueur de côté.
L’encapsulation est une technique qui consiste à considérer les objets comme des « boites noires », dont on ne manipule les attributs que via l’appel de méthodes.
Il existe de nombreuses implémentations de ce concept d’encapsulation selon les langages. Certains langages le permettent sans l’encourager (c’est par exemple le cas de Python avec son principe « We are all adults »). La majorité des langages objet utilisent les niveaux de visibilité private
, protected
et public
pour définir l’accessibilité des attributs et méthodes d’un objet depuis l’extérieur de la classe.
Voici une illustration de cette façon de faire en Java :
class Personne {
private String nom;
private String prenom;
}
public class test {
public static void main(String[] args) {
Personne pascal = new Personne();
system.out.println(pascal.nom);
}
}
La compilation échoue avec ce message d’erreur :
/MonProgramme.java:5: error: nom has private access in Personne
System.out.println(pascal.nom);
^
1 error
Pour respecter le principe d’encapsulation, on définit avec une méthode permettant d’accéder à l’attribut privé au sein de la classe Personne
(en l’occurrence on appelle ce genre de méthode d’accès… un « accesseurs ») :
class Personne {
private String nom;
private String prenom;
public String getNom() {
return this.nom;
}
}
Puis, on passe systématiquement par cette méthode pour accéder aux attributs privés :
public class MonProgramme {
public static void main(String[] args) {
Personne pascal = new Personne();
system.out.println(pascal.getNom());
}
}