# Qu’est-ce que la programmation orientée objet (OOP) ?

La programmation orientée objet (OOP) est utilisée partout. En effet, les technologies orientées objet servent à créer des systèmes d’exploitation, des logiciels commerciaux et des logiciels libres. Les **avantages de l’OOP n’apparaissent que lorsqu’un projet atteint une certaine complexité**. Le style de programmation orienté objet est toujours l’un des [paradigmes de programmation](https://www.ionos.fr/digitalguide/sites-internet/developpement-web/paradigmes-de-programmation/) dominants.

## Qu’est-ce que la programmation orientée objet et à quoi sert-elle ?

Le terme « programmation orientée objet » (« Object-Oriented Programming » ou OOP en anglais) a été inventé vers la fin des années 1960 par la légende de la programmation Alan Kay. Ce dernier a participé au développement du langage de programmation orienté objet **Smalltalk**, directement influencé par Simula, le premier langage à proposer des fonctionnalités d’OPP. Les idées fondamentales de Smalltalk continuent d’influencer les fonctionnalités d’OPP des langages de programmation modernes. Parmi les langages influencés par Smalltalk figurent notamment Ruby, Python, Go et Swift.

La programmation orientée objet fait partie des paradigmes de programmation dominants, au même titre que la [programmation fonctionnelle](https://www.ionos.fr/digitalguide/sites-internet/developpement-web/programmation-fonctionnelle/) (FP), très populaire. Les approches de programmation peuvent être classées en deux grands courants : « impératif » et « déclaratif ». Le **OOP est une expression du style de programmation impératif** et un développement spécifique de la programmation procédurale :

1. **[Programmation impérative](https://www.ionos.fr/digitalguide/sites-internet/developpement-web/programmation-imperative/)** : décrire en différentes étapes comment résoudre un problème. Exemple : algorithme

- Programmation structurée
- Programmation procédurale
- Programmation orientée objet

2. **[Programmation déclarative](https://www.ionos.fr/digitalguide/sites-internet/developpement-web/programmation-declarative/)** : générer des résultats selon certaines règles. Exemple : requête SQL

- Programmation fonctionnelle
- Programmation spécifique au domaine

Note Les termes « procédure » et « fonction » sont souvent utilisés comme synonymes. Dans les deux cas, il s’agit de blocs de code exécutables qui peuvent recevoir des arguments. La différence réside dans le fait que les fonctions renvoient une valeur, ce qui n’est pas le cas des procédures. Tous les langages ne prennent pas explicitement en charge les procédures.

En principe, il est **possible de résoudre n’importe quel problème de programmation avec n’importe quel paradigme**, car tous les paradigmes sont « Turing-complet ». L’élément limitant n’est donc pas la machine, mais l’homme. Les programmeurs ou les équipes de programmation ne peuvent appréhender qu’une quantité limitée de complexité. Ils utilisent donc des abstractions pour maîtriser la complexité. En fonction du domaine d’application et de la problématique, un style de programmation conviendra mieux qu’un autre.

La plupart des langages modernes sont des **langages dits « multi-paradigmes »**, c’est-à-dire qu’ils permettent de programmer dans plusieurs styles de programmation. À l’inverse, certains langages ne supportent qu’un seul style de programmation. C’est notamment le cas des langages strictement fonctionnels comme Haskell :

<table>
  <thead>
    <tr>
      <th></th>
      <th><strong>Paradigme</strong></th>
      <th><strong>Caractéristiques</strong></th>
      <th><strong>Particulièrement adapté à</strong></th>
      <th><strong>Langues</strong></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Impératif</td>
      <td>OOP</td>
      <td>Objets, classes, méthodes, héritage, polymorphisme</td>
      <td>Modélisation, conception de systèmes</td>
      <td>Smalltalk, Java, Ruby, Python, Swift</td>
    </tr>
    <tr>
      <td>Impératif</td>
      <td>Procédural</td>
      <td>Flux de contrôle, itération, procédures / fonctions</td>
      <td>Traitement séquentiel des données</td>
      <td>C, Pascal, Basic</td>
    </tr>
    <tr>
      <td>Déclaratif</td>
      <td>Fonctionnel</td>
      <td>Immutabilité, fonctions pures, lambda-calcul, récursivité, système de type</td>
      <td>Traitement parallèle des données, applications mathématiques et scientifiques, analyseurs et compilateurs</td>
      <td>Lisp, Haskell, Clojure</td>
    </tr>
    <tr>
      <td>Déclaratif</td>
      <td>Langage spécifique au domaine (DSL)</td>
      <td>Expressif, grande richesse de langage</td>
      <td>Applications spécifiques au domaine</td>
      <td>SQL, CSS</td>
    </tr>
  </tbody>
</table>

Note Curieusement, même **[CSS](https://www.ionos.fr/digitalguide/sites-internet/web-design/quest-ce-que-le-css/)** est un langage Turing-complet. Cela signifie que tout calcul écrit dans un autre langage peut également être résolu en CSS.

La programmation orientée objet fait partie de la programmation impérative et **est issue de la programmation procédurale**. Cette dernière s’occupe en fait de données inertes qui sont traitées par du code exécutable :

1. **Données** : valeurs, structures de données, variables
2. **Code** : expressions, structures de contrôle, fonctions

C’est précisément là que réside la différence entre la programmation orientée objet et la programmation procédurale : **l’OOP réunit les données et les fonctions en objets**. Un objet est pour ainsi dire une structure de données vivante ; car les objets ne sont pas inertes, ils ont un comportement. Les objets sont donc comparables à des machines ou à des organismes unicellulaires. Si l’on se contente d’opérer sur des données, on interagit avec les objets ou les objets interagissent entre eux.

Illustrons la différence par un exemple. Une **variable entière en Java ou C++ ne contient qu’une valeur**. Il ne s’agit pas d’une structure de données, mais d’une « primitive » :

```Java
int number = 42;
```

Les opérations sur les primitives sont effectuées à l’aide d’opérateurs ou de fonctions définis à l’extérieur. Voici l’exemple de la fonction *successeur* qui renvoie un nombre entier au suivant :

```Java
int successor(int number) {
    return number + 1;
}
// returns `43`
successor(42)
```

En revanche, dans des **langages comme Python et Ruby**, « everything is an object » (« tout est un objet »). Même un simple nombre comprend la valeur proprement dite ainsi qu’un ensemble de méthodes qui définissent des opérations sur la valeur. Ici, l’exemple de la fonction *succ* intégrée en Ruby :

```Ruby
# returns `43`
42.succ
```

C’est tout d’abord pratique, car la **fonctionnalité est regroupée pour un seul et même type de données**. Il n’est pas possible de faire appel à une méthode qui ne correspond pas au type. Mais les méthodes peuvent faire bien plus. En Ruby, même la boucle For est réalisée comme méthode d’un nombre. Nous donnons à titre d’exemple les nombres de 51 à 42 :

```Ruby
51.downto(42) { |n| print n, ".. " }
```

D’où proviennent les méthodes ? Dans la plupart des langages, les objets sont définis par des classes. On dit que les objets sont instanciés à partir de classes, c’est pourquoi on appelle les objets des instances. Une **classe est un modèle permettant de créer des objets similaires** disposant des mêmes méthodes. Ainsi, les classes fonctionnent comme des types dans les langages d’OOP. Cela est évident dans la [programmation orientée objet en Python](https://www.ionos.fr/digitalguide/sites-internet/developpement-web/programmation-orientee-objet/); la fonction *type* renvoie une classe comme type d’une valeur :

```Python
type(42) # <class 'int'>
type('Walter White') # <class 'str'>
```

## Comment fonctionne la programmation orientée objet ?

Si l’on demande à une personne ayant un peu d’expérience en programmation en quoi consiste l’OOP, la réponse sera probablement que cela a à voir avec les classes. En réalité, les classes ne sont pas le cœur du sujet. Les **idées de base** de la programmation orientée objet d’Alan Kay sont plus simples et peuvent être résumées comme suit :

1. Les objets **encapsulent** leur état interne.
2. Les objets reçoivent des **messages** via leurs méthodes.
3. L’allocation des méthodes se fait de manière **dynamique** au moment de l’exécution.

Examinons ces trois points de plus près.

### Les objets encapsulent leur état interne

Pour comprendre ce que l’on entend par encapsulation, prenons l’exemple d’une voiture. Une voiture présente un certain état, par exemple la charge de la batterie, le niveau de remplissage du réservoir, le moteur allumé ou non. Si nous représentons une telle voiture en tant qu’objet, les propriétés internes doivent pouvoir être modifiées exclusivement via des interfaces définies.

Regardons quelques exemples. Nous avons un objetcar\*qui représente une voiture. À l’intérieur de l’objet, l’état est**enregistré dans des variables**. L’objet gère les valeurs des variables ; on peut ainsi s’assurer que de l’énergie est dépensée pour démarrer le moteur, par exemple. Nous démarrons le moteur de la voiture en envoyant un message start :

```Python
car.start()
```

À ce stade,**c’est l’objet qui décide de ce qui va se passer ensuite** : si le moteur tourne déjà, le message est ignoré ou un message correspondant est émis. S’il n’y a pas assez de charge de batterie ou si le réservoir est vide, le moteur reste arrêté. Si toutes les conditions sont remplies, le moteur est démarré et l’état interne est adapté. Par exemple, une variable booléenne *motor\_running* est définie sur « True » et la charge de la batterie est diminuée de la charge nécessaire au démarrage. Nous montrons à quoi pourrait ressembler le code à l’intérieur de l’objet :

```Python
# starting car
motor_running = True
battery_charge -= start_charge
```

Il est important que **l’état interne ne puisse pas être modifié directement de l’extérieur**. Sinon, nous pourrions définir *motor\_running* sur « True », même si la batterie est vide. Cela relèverait de la magie et ne refléterait pas les conditions réelles.

### Envoi de messages / appel de méthodes

Comme nous l’avons vu, les objets réagissent aux messages et modifient éventuellement leur état interne en réaction. Nous appelons ces messages des **méthodes** ; d’un point de vue technique, il s’agit de fonctions liées à un objet. Le message se compose du nom de la méthode et, le cas échéant, d’autres arguments. L’objet qui reçoit est appelé récepteur. Nous exprimons le schéma général de la réception de messages par des objets comme suit :

```Python
# call a method
receiver.method(args)
```

Exemple : imaginons que nous programmons un smartphone. Différents **objets représentent différentes fonctionnalités** comme les paramètres du téléphone, la lampe de poche, un appel, un message texte, etc. Habituellement, les différents sous-composants sont à leur tour modélisés comme des objets. Ainsi, le carnet d’adresses est un objet, tout comme chaque contact qu’il contient et le numéro de téléphone d’un contact. Il est ainsi facile de modéliser des processus issus de la réalité :

```Python
# find a person in our address book
person = contacts.find('Walter White')
# let's call that person's work number
call = phone.call(person.phoneNumber('Work'))
...
# after some time, hang up the phone
call.hangUp()
```

### Allocation dynamique des méthodes

Le troisième critère essentiel de la définition initiale de l’OOP d’Alan Kay est l’allocation dynamique des méthodes au moment de l’exécution. Cela signifie que la décision concernant le **code à exécuter lors de l’appel d’une méthode** n’est prise qu’au moment de l’exécution du programme. En conséquence, le comportement d’un objet peut être modifié au moment de l’exécution.

L’allocation dynamique des méthodes a des **conséquences importantes sur l’implémentation technique des fonctionnalités de l’OOP** dans les langages de programmation. Dans la pratique, on y est généralement moins confronté. Penchons-nous néanmoins sur un exemple. Nous modélisons la lampe de poche du smartphone en tant qu’objet *flashlight*. Celle-ci réagit aux messages *on, off et intensity*:

```JavaScript
// turn on flashlight
flashlight.on()
// set flashlight intensity to 50%
flashlight.intensity(50)
// turn off flashlight
flashlight.off()
```

Supposons que la lampe de poche se casse et que nous décidions d’émettre un avertissement à chaque fois que nous voulions l’utiliser. L’approche consiste à **remplacer toutes les méthodes par une nouvelle méthode**. C’est par exemple très simple en JavaScript. Nous définissons la nouvelle fonction *out\_of\_order* et remplaçons les méthodes existantes :

```JavaScript
function out_of_order() {
    console.log('Flashlight out of order. Please service phone.')
    return false;
}
flashlight.on = out_of_order;
flashlight.off = out_of_order;
flashlight.intensity = out_of_order;
```

Si nous essayons par la suite d’interagir avec la lampe de poche, *out\_of\_order* sera toujours appelé :

```JavaScript
// calls `out_of_order()`
flashlight.on()
// calls `out_of_order()`
flashlight.intensity(50)
// calls `out_of_order()`
flashlight.off()
```

## D’où proviennent les objets ? Instanciation et initialisation

Jusqu’à présent, nous avons vu comment les objets reçoivent des messages et y réagissent. Mais d’où proviennent les objets ? Intéressons-nous maintenant à la notion centrale d’instanciation. **L’instanciation est le processus par lequel un objet prend vie**. Dans les différents langages de programmation orientée objet, il existe différents mécanismes d’instanciation. La plupart du temps, un ou plusieurs des mécanismes suivants sont utilisés :

1. Définition par objet littéral
2. Instanciation avec une fonction constructeur
3. Instanciation à partir d’une classe

JavaScript excelle dans ce domaine, car il est possible de **définir directement des objets tels que des nombres ou des chaînes de caractères en tant que littéraux**. Prenons un exemple simple : nous instancions un objet vide *person* et lui attribuons ensuite la propriété *name* ainsi qu’une méthode *greet*. Notre objet est alors en mesure de saluer une autre personne et de donner son propre nom :

```JavaScript
// instantiate empty object
let person = {};
// assign object property
person.name = "Jack";
// assign method
person.greet = function(other) {
    return `"Hi ${other}, I'm ${this.name}"`
};
// let's test
person.greet("Jim")
```

Nous avons instancié un objet unique. Cependant, nous souhaitons **souvent répéter l’instanciation afin de créer une série d’objets similaires**. Ce cas peut aussi être facilement résolu en JavaScript. Nous créons ce que l’on appelle une fonction constructeur qui, lorsqu’elle est appelée, assemble un objet. Notre fonction constructeur, appelée *Person*, prend en compte un nom et un âge et crée un nouvel objet lorsqu’elle est appelée :

```JavaScript
function Person(name, age) {
    this.name = name;
    this.age = age;
    
    this.introduce_self = function() {
        return `"I'm ${this.name}, ${this.age} years old."`
    }
}
// instantiate person
person = new Person('Walter White', 42)
// let person introduce themselves
person.introduce_self()
```

Attention à l’utilisation du **mot-clé *this***. Celui-ci se trouve également dans d’autres langages tels que Java, PHP et C++ et est souvent source de confusion pour les novices en OOP. Pour faire simple,*this*est un caractère de remplacement pour un objet instancié. Lors de l’appel d’une méthode,*this’ référence le récepteur, c’est-à-dire pointe vers une instance d’objet spécifique. D’autres langages, comme Python et Ruby, utilisent le mot-clé *self* à la place de*thi\*s, ce qui a le même effet.

De plus, en JavaScript, nous avons besoin du **mot-clé *new***pour correctement créer l’instance d’objet. On le trouve notamment en Java et en C++, qui font la distinction entre « pile » (stack) et « tas » (heap) pour le stockage de valeurs en mémoire. Dans les deux langages,*new*sert à allouer de la mémoire sur le tas. JavaScript, comme Python, place toutes les valeurs dans le tas, de sorte que*new* est en fait inutile. Cela montre donc qu’il est possible de s’en passer.

Le troisième mécanisme de création d’instances d’objets, le plus répandu, fait appel aux **classes**. Une classe remplit un rôle similaire à celui d’une fonction constructeur en JavaScript : les deux servent de modèle à partir duquel des objets similaires peuvent être instanciés si nécessaire. En même temps, dans des langages comme Python et Ruby, une classe sert de substitut aux types utilisés dans d’autres langages. Un exemple de classe est présenté ci-dessous.

## Quels sont les avantages et les inconvénients de l’OOP ?

La programmation orientée objet est **de plus en plus critiquée** depuis le début du 21ème siècle. Les langages fonctionnels modernes, dotés d’une immutabilité et de systèmes de types forts, sont considérés comme plus stables, plus fiables et plus performants. Cependant, l’OOP est très répandue et présente de nombreux avantages. Il est important de choisir le bon outil pour chaque problème, plutôt que de miser sur une seule méthodologie.

### Avantage : l’encapsulation

L’un des avantages immédiats de l’OOP est le **regroupement des fonctionnalités**. Au lieu de regrouper plusieurs variables et fonctions dans une collection aléatoire, il est possible de les relier en unités cohérentes. Nous illustrons la différence par un exemple : nous modélisons un bus et utilisons pour cela deux variables et une fonction. Les passagers peuvent monter dans le bus jusqu’à ce qu’il soit plein :

```Python
# list to hold the passengers
bus_passengers = []
# maximum number of passengers
bus_capacity = 12
# add another passenger
def take_bus(passenger)
    if len(bus_passengers) < bus_capacity:
        bus_passengers.append(passenger)
    else:
        raise Exception("Bus is full")
```

Le code fonctionne, mais il est **problématique**. La fonction *take\_bus* accède aux variables *bus\_passengers et bus\_capacity* sans que celles-ci soient transmises comme arguments. Cela pose des problèmes pour les codes volumineux, car les variables doivent être soit fournies globalement, soit passées à chaque appel. Il est également possible de « tricher ». Nous pouvons continuer à ajouter des passagers au bus alors qu’il est déjà plein :

```Python
# bus is full
assert len(bus_passengers) == bus_capacity
# will raise exception, won't add passenger
take_bus(passenger)
# we cheat, adding an additional passenger directly
bus_passengers.append(passenger)
# now bus is over capacity
assert len(bus_passengers) > bus_capacity
```

De plus, rien ne nous empêche d’augmenter la capacité du bus. Cependant, cela **transgresse la réalité physique**, car un bus existant a une capacité limitée qui ne peut pas être modifiée à volonté par la suite :

```Python
# can't do this in reality
bus_capacity += 1
```

**L’encapsulation de l’état interne des objets protège contre les modifications absurdes ou non souhaitées**. Voici la même fonctionnalité en code orienté objet. Nous définissons une classe bus et instancions un bus à capacité limitée. L’ajout de passagers n’est possible que par la méthode correspondante :

```Python
class Bus():
    def __init__(self, capacity):
        self._passengers = []
        self._capacity = capacity
    
    def enter(self, passenger):
        if len(self._passengers) < self._capacity:
            self._passengers.append(passenger)
            print(f"{passenger} has entered the bus")
        else:
            raise Exception("Bus is full")
# instantiate bus with given capacity
bus = Bus(2)
bus.enter("Jack")
bus.enter("Jim")
# will fail, bus is full
bus.enter("John")
```

### Avantage : modéliser des systèmes

La programmation orientée objet se prête particulièrement bien à la modélisation de systèmes. L’intuition de l’OOP est humaine, car nous pensons également en termes d’objets, qui peuvent être classés par catégories. Les objets peuvent être aussi bien des choses physiques que des concepts abstraits.

**L’héritage via des hiérarchies de classes**, présent dans de nombreux langages d’OOP, **reflète également des schémas de pensée humains**. Illustrons ce dernier point par un exemple. Un animal est un concept abstrait. Les animaux qui apparaissent réellement sont toujours des expressions concrètes d’une espèce. Selon l’espèce, les animaux ont des caractéristiques différentes. Un chien ne peut ni grimper ni voler, il est donc limité à des mouvements dans un espace à deux dimensions :

```Python
# abstract base class
class Animal():
    def move_to(self, coords):
        pass
# derived class
class Dog(Animal):
    def move_to(self, coords):
        match coords:
            # dogs can't fly nor climb
            case (x, y):
                self._walk_to(coords)
# derived class
class Bird(Animal):
    def move_to(self, coords):
        match coords:
            # birds can walk
            case (x, y):
                self._walk_to(coords)
            # birds can fly
            case (x, z, y):
                self._fly_to(coords)
```

### Inconvénients de la programmation orientée objet

L’un des inconvénients immédiats de l’OPP est son **jargon, difficile à comprendre au départ** On se voit contraint d’apprendre des concepts nouveaux, dont le sens et le but ne sont souvent pas compréhensibles dans des exemples simples. Il est donc facile de commettre des erreurs ; la modélisation des hiérarchies d’héritage demande beaucoup d’habileté et d’expérience.

L’une des critiques les plus fréquentes à l’encontre de l’OOP est l’encapsulation de l’état interne, censée être un avantage. Cela entraîne des **difficultés lors de la parallélisation du code OOP**. En effet, si un objet est transmis à plusieurs fonctions parallèles, l’état interne pourrait changer entre les appels de fonction. De plus, il est parfois nécessaire d’accéder, au sein d’un programme, à des informations encapsulées ailleurs.

La nature dynamique de la programmation orientée objet entraîne généralement des **pertes de performance**. En effet, il est moins possible de procéder à des optimisations statiques. De même, les systèmes de types des langages OOP purs, qui ont tendance à être moins prononcés, rendent certaines vérifications statiques impossibles. Ainsi, les erreurs ne sont visibles qu’au moment de l’exécution. Les développements les plus récents, comme le langage JavaScript TypeScript, permettent d’y remédier.

## Quels sont les langages de programmation qui sont adaptés à l’OOP ?

Presque **tous les langages multi-paradigmes conviennent à la programmation orientée objet**. Les [langages de programmation Web](https://www.ionos.fr/digitalguide/sites-internet/developpement-web/langages-de-programmation-web/) PHP, Ruby, Python et JavaScript en font partie. En revanche, les principes de l’OOP sont en grande partie incompatibles avec l’algèbre relationnelle sur laquelle repose SQL. Pour combler l’« Impedance Mismatch », on utilise des couches de traduction spéciales, connues sous le nom de « Object Relational Mapper » (ORM).

Même les langages purement fonctionnels comme Haskell n’apportent généralement pas de support natif pour l’OOP. Pour appliquer [l’OOP en C](https://www.ionos.fr/digitalguide/sites-internet/developpement-web/oop-in-c/), il faut déployer des efforts considérables. Il est intéressant de noter qu’il existe un langage moderne, **Rust**, qui se passe de classes. À la place, on utilise *struct et enum* comme structures de données, dont le comportement est défini par un mot-clé *impl*. Les comportements peuvent être regroupés à l’aide de ce que l’on appelle des traits ; l’héritage et le polymorphisme sont également représentés de cette manière. La conception du langage reflète la meilleure pratique de l’OOP : « Composition over Inheritance ».


This is a markdown version of: [https://www.ionos.fr/digitalguide/sites-internet/developpement-web/oop-programmation-orientee-objet/](https://www.ionos.fr/digitalguide/sites-internet/developpement-web/oop-programmation-orientee-objet/) for AI/LLM consumption.