La pro­gram­ma­tion orientée objet (OOP) est utilisée partout. En effet, les tech­no­lo­gies orientées objet servent à créer des systèmes d’ex­ploi­ta­tion, des logiciels com­mer­ciaux et des logiciels libres. Les avantages de l’OOP n’ap­pa­rais­sent que lorsqu’un projet atteint une certaine com­plexité. Le style de pro­gram­ma­tion orienté objet est toujours l’un des pa­ra­digmes de pro­gram­ma­tion dominants.

Qu’est-ce que la pro­gram­ma­tion orientée objet et à quoi sert-elle ?

Le terme « pro­gram­ma­tion orientée objet » (« Object-Oriented Pro­gram­ming » ou OOP en anglais) a été inventé vers la fin des années 1960 par la légende de la pro­gram­ma­tion Alan Kay. Ce dernier a participé au dé­ve­lop­pe­ment du langage de pro­gram­ma­tion orienté objet Smalltalk, di­rec­te­ment influencé par Simula, le premier langage à proposer des fonc­tion­na­li­tés d’OPP. Les idées fon­da­men­tales de Smalltalk con­ti­nuent d’in­fluen­cer les fonc­tion­na­li­tés d’OPP des langages de pro­gram­ma­tion modernes. Parmi les langages in­fluen­cés par Smalltalk figurent notamment Ruby, Python, Go et Swift.

La pro­gram­ma­tion orientée objet fait partie des pa­ra­digmes de pro­gram­ma­tion dominants, au même titre que la pro­gram­ma­tion fonc­tion­nelle (FP), très populaire. Les approches de pro­gram­ma­tion peuvent être classées en deux grands courants : « impératif » et « dé­cla­ra­tif ». Le OOP est une ex­pres­sion du style de pro­gram­ma­tion impératif et un dé­ve­lop­pe­ment spé­ci­fique de la pro­gram­ma­tion pro­cé­du­rale :

  1. Pro­gram­ma­tion im­pé­ra­tive : décrire en dif­fé­rentes étapes comment résoudre un problème. Exemple : al­go­rithme
  • Pro­gram­ma­tion struc­tu­rée
    • Pro­gram­ma­tion pro­cé­du­rale
      • Pro­gram­ma­tion orientée objet
  1. Pro­gram­ma­tion dé­cla­ra­tive : générer des résultats selon certaines règles. Exemple : requête SQL
  • Pro­gram­ma­tion fonc­tion­nelle
  • Pro­gram­ma­tion spé­ci­fique 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é­cu­tables qui peuvent recevoir des arguments. La dif­fé­rence réside dans le fait que les fonctions renvoient une valeur, ce qui n’est pas le cas des pro­cé­dures. Tous les langages ne prennent pas ex­pli­ci­te­ment en charge les pro­cé­dures.

En principe, il est possible de résoudre n’importe quel problème de pro­gram­ma­tion avec n’importe quel paradigme, car tous les pa­ra­digmes sont « Turing-complet ». L’élément limitant n’est donc pas la machine, mais l’homme. Les pro­gram­meurs ou les équipes de pro­gram­ma­tion ne peuvent ap­pré­hen­der qu’une quantité limitée de com­plexité. Ils utilisent donc des abs­trac­tions pour maîtriser la com­plexité. En fonction du domaine d’ap­pli­ca­tion et de la pro­blé­ma­tique, un style de pro­gram­ma­tion con­vien­dra mieux qu’un autre.

La plupart des langages modernes sont des langages dits « multi-pa­ra­digmes », c’est-à-dire qu’ils per­met­tent de pro­gram­mer dans plusieurs styles de pro­gram­ma­tion. À l’inverse, certains langages ne sup­por­tent qu’un seul style de pro­gram­ma­tion. C’est notamment le cas des langages stric­te­ment fonc­tion­nels comme Haskell :

Paradigme Ca­rac­té­ris­tiques Par­ti­cu­liè­re­ment adapté à Langues
Impératif OOP Objets, classes, méthodes, héritage, po­ly­mor­phisme Mo­dé­li­sa­tion, con­cep­tion de systèmes Smalltalk, Java, Ruby, Python, Swift
Impératif Pro­cé­du­ral Flux de contrôle, itération, pro­cé­dures / fonctions Trai­te­ment sé­quen­tiel des données C, Pascal, Basic
Dé­cla­ra­tif Fonc­tion­nel Im­mu­ta­bi­lité, fonctions pures, lambda-calcul, ré­cur­si­vité, système de type Trai­te­ment parallèle des données, ap­pli­ca­tions ma­thé­ma­tiques et scien­ti­fiques, ana­ly­seurs et com­pi­la­teurs Lisp, Haskell, Clojure
Dé­cla­ra­tif Langage spé­ci­fique au domaine (DSL) Expressif, grande richesse de langage Ap­pli­ca­tions spé­ci­fiques au domaine SQL, CSS
Note

Cu­rieu­se­ment, même CSS est un langage Turing-complet. Cela signifie que tout calcul écrit dans un autre langage peut également être résolu en CSS.

La pro­gram­ma­tion orientée objet fait partie de la pro­gram­ma­tion im­pé­ra­tive et est issue de la pro­gram­ma­tion pro­cé­du­rale. Cette dernière s’occupe en fait de données inertes qui sont traitées par du code exé­cu­table :

  1. Données : valeurs, struc­tures de données, variables
  2. Code : ex­pres­sions, struc­tures de contrôle, fonctions

C’est pré­ci­sé­ment là que réside la dif­fé­rence entre la pro­gram­ma­tion orientée objet et la pro­gram­ma­tion pro­cé­du­rale : 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 com­por­te­ment. Les objets sont donc com­pa­rables à des machines ou à des or­ga­nismes uni­cel­lu­laires. Si l’on se contente d’opérer sur des données, on interagit avec les objets ou les objets in­te­ra­gis­sent entre eux.

Il­lus­trons la dif­fé­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 » :

int number = 42;
Java

Les opé­ra­tions sur les pri­mi­tives sont ef­fec­tuées à l’aide d’opé­ra­teurs ou de fonctions définis à l’extérieur. Voici l’exemple de la fonction suc­ces­seur qui renvoie un nombre entier au suivant :

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

En revanche, dans des langages comme Python et Ruby, « eve­ry­thing is an object » (« tout est un objet »). Même un simple nombre comprend la valeur pro­pre­ment dite ainsi qu’un ensemble de méthodes qui dé­fi­nis­sent des opé­ra­tions sur la valeur. Ici, l’exemple de la fonction succ intégrée en Ruby :

# returns `43`
42.succ
Ruby

C’est tout d’abord pratique, car la fonc­tion­na­lité 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 cor­res­pond 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 :

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

D’où pro­vien­nent les méthodes ? Dans la plupart des langages, les objets sont définis par des classes. On dit que les objets sont ins­tan­ciés à partir de classes, c’est pourquoi on appelle les objets des instances. Une classe est un modèle per­met­tant de créer des objets si­mi­laires disposant des mêmes méthodes. Ainsi, les classes fonc­tion­nent comme des types dans les langages d’OOP. Cela est évident dans la pro­gram­ma­tion orientée objet en Python; la fonction type renvoie une classe comme type d’une valeur :

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

Comment fonc­tionne la pro­gram­ma­tion orientée objet ?

Si l’on demande à une personne ayant un peu d’ex­pé­rience en pro­gram­ma­tion en quoi consiste l’OOP, la réponse sera pro­ba­ble­ment 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 pro­gram­ma­tion orientée objet d’Alan Kay sont plus simples et peuvent être résumées comme suit :

  1. Les objets en­cap­su­lent leur état interne.
  2. Les objets reçoivent des messages via leurs méthodes.
  3. L’al­lo­ca­tion 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 en­cap­su­lent leur état interne

Pour com­prendre ce que l’on entend par en­cap­su­la­tion, prenons l’exemple d’une voiture. Une voiture présente un certain état, par exemple la charge de la batterie, le niveau de rem­plis­sage du réservoir, le moteur allumé ou non. Si nous re­pré­sen­tons une telle voiture en tant qu’objet, les pro­prié­tés internes doivent pouvoir être modifiées ex­clu­si­ve­ment via des in­ter­faces définies.

Regardons quelques exemples. Nous avons un objetcar*qui re­pré­sente une voiture. À l’intérieur de l’objet, l’état esten­re­gis­tré 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 :

car.start()
Python

À 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 cor­res­pon­dant 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 con­di­tions 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é­ces­saire au démarrage. Nous montrons à quoi pourrait res­sem­bler le code à l’intérieur de l’objet :

# starting car
motor_running = True
battery_charge -= start_charge
Python

Il est important que l’état interne ne puisse pas être modifié di­rec­te­ment de l’extérieur. Sinon, nous pourrions définir motor_running sur « True », même si la batterie est vide. Cela re­lè­ve­rait de la magie et ne re­flé­te­rait pas les con­di­tions réelles.

Envoi de messages / appel de méthodes

Comme nous l’avons vu, les objets réa­gis­sent aux messages et modifient éven­tuel­le­ment 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 :

# call a method
receiver.method(args)
Python

Exemple : imaginons que nous pro­gram­mons un smart­phone. Dif­fé­rents objets re­pré­sen­tent dif­fé­rentes fonc­tion­na­li­tés comme les pa­ra­mètres du téléphone, la lampe de poche, un appel, un message texte, etc. Ha­bi­tuel­le­ment, les dif­fé­rents sous-com­po­sants 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é :

# 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()
Python

Al­lo­ca­tion dynamique des méthodes

Le troisième critère essentiel de la dé­fi­ni­tion initiale de l’OOP d’Alan Kay est l’al­lo­ca­tion dynamique des méthodes au moment de l’exécution. Cela signifie que la décision con­cer­nant 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 con­sé­quence, le com­por­te­ment d’un objet peut être modifié au moment de l’exécution.

L’al­lo­ca­tion dynamique des méthodes a des con­sé­quences im­por­tantes sur l’im­plé­men­ta­tion technique des fonc­tion­na­li­tés de l’OOP dans les langages de pro­gram­ma­tion. Dans la pratique, on y est gé­né­ra­le­ment moins confronté. Penchons-nous néanmoins sur un exemple. Nous mo­dé­li­sons la lampe de poche du smart­phone en tant qu’objet fla­sh­light. Celle-ci réagit aux messages on, off et intensity:

// turn on flashlight
flashlight.on()
// set flashlight intensity to 50%
flashlight.intensity(50)
// turn off flashlight
flashlight.off()
Ja­vaS­cript

Supposons que la lampe de poche se casse et que nous décidions d’émettre un aver­tis­se­ment à 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 Ja­vaS­cript. Nous dé­fi­nis­sons la nouvelle fonction out_of_order et rem­pla­çons les méthodes exis­tantes :

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;
Ja­vaS­cript

Si nous essayons par la suite d’interagir avec la lampe de poche, out_of_order sera toujours appelé :

// calls `out_of_order()`
flashlight.on()
// calls `out_of_order()`
flashlight.intensity(50)
// calls `out_of_order()`
flashlight.off()
Ja­vaS­cript

D’où pro­vien­nent les objets ? Ins­tan­cia­tion et ini­tia­li­sa­tion

Jusqu’à présent, nous avons vu comment les objets reçoivent des messages et y réa­gis­sent. Mais d’où pro­vien­nent les objets ? In­té­res­sons-nous main­te­nant à la notion centrale d’ins­tan­cia­tion. L’ins­tan­cia­tion est le processus par lequel un objet prend vie. Dans les dif­fé­rents langages de pro­gram­ma­tion orientée objet, il existe dif­fé­rents mé­ca­nismes d’ins­tan­cia­tion. La plupart du temps, un ou plusieurs des mé­ca­nismes suivants sont utilisés :

  1. Dé­fi­ni­tion par objet littéral
  2. Ins­tan­cia­tion avec une fonction cons­truc­teur
  3. Ins­tan­cia­tion à partir d’une classe

Ja­vaS­cript excelle dans ce domaine, car il est possible de définir di­rec­te­ment des objets tels que des nombres ou des chaînes de ca­rac­tères en tant que littéraux. Prenons un exemple simple : nous ins­tan­cions un objet vide person et lui at­tri­buons 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 :

// 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")
Ja­vaS­cript

Nous avons instancié un objet unique. Cependant, nous sou­hai­tons souvent répéter l’ins­tan­cia­tion afin de créer une série d’objets si­mi­laires. Ce cas peut aussi être fa­ci­le­ment résolu en Ja­vaS­cript. Nous créons ce que l’on appelle une fonction cons­truc­teur qui, lorsqu’elle est appelée, assemble un objet. Notre fonction cons­truc­teur, appelée Person, prend en compte un nom et un âge et crée un nouvel objet lorsqu’elle est appelée :

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()
Ja­vaS­cript

Attention à l’uti­li­sa­tion 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,thisest un caractère de rem­pla­ce­ment 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é­ci­fique. D’autres langages, comme Python et Ruby, utilisent le mot-clé self à la place dethi*s, ce qui a le même effet.

De plus, en Ja­vaS­cript, nous avons besoin du mot-clé newpour cor­rec­te­ment créer l’instance d’objet. On le trouve notamment en Java et en C++, qui font la dis­tinc­tion entre « pile » (stack) et « tas » (heap) pour le stockage de valeurs en mémoire. Dans les deux langages,newsert à allouer de la mémoire sur le tas. Ja­vaS­cript, comme Python, place toutes les valeurs dans le tas, de sorte quenew 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 cons­truc­teur en Ja­vaS­cript : les deux servent de modèle à partir duquel des objets si­mi­laires peuvent être ins­tan­ciés si né­ces­saire. 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 in­con­vé­nients de l’OOP ?

La pro­gram­ma­tion orientée objet est de plus en plus critiquée depuis le début du 21ème siècle. Les langages fonc­tion­nels modernes, dotés d’une im­mu­ta­bi­lité et de systèmes de types forts, sont con­si­dé­rés comme plus stables, plus fiables et plus per­for­mants. 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é­tho­do­lo­gie.

Avantage : l’en­cap­su­la­tion

L’un des avantages immédiats de l’OOP est le re­grou­pe­ment des fonc­tion­na­li­tés. Au lieu de regrouper plusieurs variables et fonctions dans une col­lec­tion aléatoire, il est possible de les relier en unités co­hé­rentes. Nous il­lus­trons la dif­fé­rence par un exemple : nous mo­dé­li­sons un bus et utilisons pour cela deux variables et une fonction. Les passagers peuvent monter dans le bus jusqu’à ce qu’il soit plein :

# 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")
Python

Le code fonc­tionne, mais il est pro­blé­ma­tique. La fonction take_bus accède aux variables bus_pas­sen­gers et bus_capacity sans que celles-ci soient trans­mises comme arguments. Cela pose des problèmes pour les codes vo­lu­mi­neux, car les variables doivent être soit fournies glo­ba­le­ment, 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 :

# 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
Python

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

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

L’en­cap­su­la­tion de l’état interne des objets protège contre les mo­di­fi­ca­tions absurdes ou non sou­hai­tées. Voici la même fonc­tion­na­lité en code orienté objet. Nous dé­fi­nis­sons une classe bus et ins­tan­cions un bus à capacité limitée. L’ajout de passagers n’est possible que par la méthode cor­res­pon­dante :

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")
Python

Avantage : modéliser des systèmes

La pro­gram­ma­tion orientée objet se prête par­ti­cu­liè­re­ment bien à la mo­dé­li­sa­tion de systèmes. L’intuition de l’OOP est humaine, car nous pensons également en termes d’objets, qui peuvent être classés par ca­té­go­ries. Les objets peuvent être aussi bien des choses physiques que des concepts abstraits.

L’héritage via des hié­rar­chies de classes, présent dans de nombreux langages d’OOP, reflète également des schémas de pensée humains. Il­lus­trons ce dernier point par un exemple. Un animal est un concept abstrait. Les animaux qui ap­pa­rais­sent réel­le­ment sont toujours des ex­pres­sions concrètes d’une espèce. Selon l’espèce, les animaux ont des ca­rac­té­ris­tiques dif­fé­rentes. Un chien ne peut ni grimper ni voler, il est donc limité à des mou­ve­ments dans un espace à deux di­men­sions :

# 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)
Python

In­con­vé­nients de la pro­gram­ma­tion orientée objet

L’un des in­con­vé­nients immédiats de l’OPP est son jargon, difficile à com­prendre au départ On se voit contraint d’apprendre des concepts nouveaux, dont le sens et le but ne sont souvent pas com­pré­hen­sibles dans des exemples simples. Il est donc facile de commettre des erreurs ; la mo­dé­li­sa­tion des hié­rar­chies d’héritage demande beaucoup d’habileté et d’ex­pé­rience.

L’une des critiques les plus fré­quentes à l’encontre de l’OOP est l’en­cap­su­la­tion de l’état interne, censée être un avantage. Cela entraîne des dif­fi­cul­tés lors de la pa­ral­lé­li­sa­tion du code OOP. En effet, si un objet est transmis à plusieurs fonctions pa­ral­lèles, l’état interne pourrait changer entre les appels de fonction. De plus, il est parfois né­ces­saire d’accéder, au sein d’un programme, à des in­for­ma­tions en­cap­su­lées ailleurs.

La nature dynamique de la pro­gram­ma­tion orientée objet entraîne gé­né­ra­le­ment des pertes de per­for­mance. En effet, il est moins possible de procéder à des op­ti­mi­sa­tions statiques. De même, les systèmes de types des langages OOP purs, qui ont tendance à être moins prononcés, rendent certaines vé­ri­fi­ca­tions statiques im­pos­sibles. Ainsi, les erreurs ne sont visibles qu’au moment de l’exécution. Les dé­ve­lop­pe­ments les plus récents, comme le langage Ja­vaS­cript Ty­peS­cript, per­met­tent d’y remédier.

Quels sont les langages de pro­gram­ma­tion qui sont adaptés à l’OOP ?

Presque tous les langages multi-pa­ra­digmes con­vien­nent à la pro­gram­ma­tion orientée objet. Les langages de pro­gram­ma­tion Web PHP, Ruby, Python et Ja­vaS­cript en font partie. En revanche, les principes de l’OOP sont en grande partie in­com­pa­tibles avec l’algèbre re­la­tion­nelle sur laquelle repose SQL. Pour combler l’« Impedance Mismatch », on utilise des couches de tra­duc­tion spéciales, connues sous le nom de « Object Re­la­tio­nal Mapper » (ORM).

Même les langages purement fonc­tion­nels comme Haskell n’apportent gé­né­ra­le­ment pas de support natif pour l’OOP. Pour appliquer l’OOP en C, il faut déployer des efforts con­si­dé­rables. Il est in­té­res­sant de noter qu’il existe un langage moderne, Rust, qui se passe de classes. À la place, on utilise struct et enum comme struc­tures de données, dont le com­por­te­ment est défini par un mot-clé impl. Les com­por­te­ments peuvent être regroupés à l’aide de ce que l’on appelle des traits ; l’héritage et le po­ly­mor­phisme sont également re­pré­sen­tés de cette manière. La con­cep­tion du langage reflète la meilleure pratique de l’OOP : « Com­po­si­tion over In­he­ri­tance ».

Aller au menu principal