La programmation orientée objet

La programmation orientée objet (abrégée par « POO ») est un paradigme de programmation, inventé dans les année 60. Il est très largement utilisé aujourd'hui.

Un peu d’histoire

Ole-Johan Dahl

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.

Grands principes du paradigme objet

La notion d’objet

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.

Classes et prototypes

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 :

  • Les langages objets à classes
  • Les langages objets à prototypes

Classe

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.

Note – À ce stade de la lecture, vous devez sans doute piquer du nez pour la première fois, et vous dire que ces descriptions d’objets et de classes sont bien verbeuses. Pas de panique (pas vu pas pris ;). Souvenez-vous que vous êtes en train de suivre une formation UML : la suite va devenir plus synthétique, c’est justement l’intérêt d’UML !

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();
    }

}

Prototype

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"

Héritage

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.

Polymorphisme

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.

Overriding ou surcharge

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.

Overloading ou polymorphisme paramétrique

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é.

Encapsulation

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 :

Personne.java

class Personne {
    private String nom;
    private String prenom;
}

MonProgramme.java

public class test {

    public static void main(String[] args) {
        Personne pascal = new Personne();
        system.out.println(pascal.nom);
    }

}

Résultat

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());
    }

}

Lectures conseillées

Article

La programmation orientée objet. Cours et exercices UML 2 avec Java, C#, C++, Python, PHP et LINQ.

Voir

Article

Apprendre la Programmation Orientée Objet avec le langage Python - (avec exercices pratiques et corrigés)

Voir

Article

Fondements de la Programmation Orientée Objet avec Java 8

Voir

Testez-vous !

B hérite de A. Qui est la classe mère de B ?
  • object
  • A
  • void
  • nil
  • B
Si B hérite de A et que A a 5 attributs, combien en a B ?
  • On ne peut pas le dire exactement sans voir le code de B.
  • Au moins 5
  • Au moins 6
  • Moins de 5
  • Il n’a pas d’attributs puisque B est une classe fille.
Pour ajouter une fonctionnalité à un programme développé selon le paradigme objet, on…
  • Peut créer une nouvelle classe
  • Doit créer une nouvelle classe
  • Peut créer une nouvelle méthode
  • On ne peut pas le faire !
Soit l’attribut private String prenom; de la classe Java Personne.
  • Je peux accéder à prenom depuis une méthode de Personne.
  • Je ne peux pas accéder à prenom depuis une méthode d’une classe fille de Personne.
  • Si p est une instance de Personne, je peux écrire x = p.prenom dans le programme principal.
Pour respecter le principe d’encapsulation, comment différents objets de différentes classes interagissent ?
  • À l’aide de méthodes
  • En utilisant des références croisées
  • À l’aide de pointeurs
Le principe d’abstraction est permis par quel mécanisme ?
  • Héritage
  • Encapsulation
  • Prototypage dynamique
  • Opérationnalisation d’ontologie

Commentaires