Les programmations orientées objet

/img/post/oop.jpg

La Programmation Orienté Objet (POO pour les intimes) est de nos jours la lingua franca de la programmation dite impérative. Pourtant, fort est de constater que celle couramment usitée est loin de l’approche définie par son auteur, Alan Kay, au point que l’on peut dire qu’il existe actuellement en fait deux approches orientées objet !

En quoi les deux approches diffèrent elles ; On pourrait résumer cette différence par ceci :

  • dans l’approche la plus pratiquée, l’accent du développement est mis sur la structuration des objets avec une volonté d’encapsuler leur état, de fusionner les concepts de type et de module dans un seul moule, la classe. Débutant avec Simula 67, elle est de nos jours commune et véhiculée par des langages comme C++ ou Java.
  • dans l’approche originale, l’accent est au contraire mis sur le comportement des objets, caractérisé par l’ensemble des messages qu’ils comprennent, la manière dont ils y répondent et leur changement d’état étant encapsulé ; la structuration est reléguée au second rang. Initiée avec Smalltalk, elle est véhiculée de nos jours par des langages comme Smalltalk, Io ou Ruby.

D’où vient cette différence ? Une explication peut être donnée par ces propos d’Alan Kay :

The “official” computer science world started to regard Simula as a possible vehicle for defining abstract data types (even by one of its inventors), and it formed much of the later backbone of ADA. […] Instead, the objects should be presented as site of higher level behaviors more appropriate for use as dynamic components.

A l’origine, les concepteurs de Smalltalk et pères de la POO se sont inspirés des techniques utilisées dans d’autres langages (Lisp, Sketchpad, JOSS, …) pour implémenter les concepts sous-jacents à la POO, et parmi ceux-là les constructions utilisées dans Simula les ont fortement inspirés. A cette époque, on travaillait sur un moyen de structurer le code à l’aide de types abstraits de données. C’est dans ce contexte que certains, comme Ole-Johan Dahl et Kristen Nygaard, les auteurs de Simula, ne virent de la POO que comme une meilleure structuration du code à l’aide de constructions idoines : classes de données, instanciation, sous-typage, attachement dynamique (ces deux derniers permettant le polymorphisme), etc. C’est sous cette tendance qu’ont émergés d’autres langages comme Pascal Object ou C++ qui, à leur tour, ont influencé plusieurs générations de développeurs. C’est ainsi que, pour beaucoup, la POO se résume à écrire des structures (les classes) regroupant à la fois les champs (appelés attributs) et les opérations (appelées méthodes), dont l’interface (les parties accessibles depuis l’extérieur) forme le type ; cette approche est plutôt une programmation orientée classe dans laquelle la classe fusionne le concept de module avec celui de type.

Pour mieux comprendre les concepts sous-jacents à la POO et ce qu’avait en tête son auteur, je recommande fortement la lecture de l’histoire de Smalltalk. On peut y lire, entre autre, que la POO est née de plusieurs idées, dont l’une d’elle peut être résumée par ces propos de Bob Barton :

The basic principal of recursive design is to make the parts have the same power as the whole.

Et dans leur vision, les objets sont perçus comme des cellules d’un organisme cellulaire ou des petits ordinateurs dans un réseau par le biais duquel ils interagissent par messages.

Plus concrètement, dans cet échange de mails, Alan Kay expliquent ce qu’est la POO :

OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme late-binding of all things.

Alan Kay ne parle aucunement de structuration. Il met en avant d’abord les concepts de messages, puis d’encapuslation à la fois des effets de bords et des détails techniques et d’implémentation, et de … résolution retardée ; L’expression «extreme late-binding of all things» peut surprendre mais est essentielle pour plusieurs choses dont le polymorphisme.

Pour comprendre ceci, il faut d’abord savoir qu’en POO le concept de message est décoléré de celui de méthode : une méthode est la réponse propre à l’objet à un message donné (autrement dit son d’implémentation). Ensuite, tout objet qui sait répondre à un message, quelque soit son type, doit pouvoir être utilisé comme receveur. Donc, la méthode à exécuter en réponse à un message ne peut être connue que lorsque le type réel de l’objet est identifié, ce qui ne peut se faire que tardivement, c’est-à-dire lorsque celui-ci est effectivement inféré (le message est transmis à l’objet).

La résolution tardive permet à un objet dont le type satisfait l’interface attendue d’être utilisé en tant que receveur et ceci sans nécessairement entrer dans une relation de sous-typage avec l’interface ; celle-ci peut d’ailleurs être dynamique au sens où elle définit uniquement les messages attendus dans un contexte donné. On dit alors que le type de l’objet est un élément de la classe de types représentée par cette interface ; autrement dit il satisfait l’interface définie par la classe de types.

En effet, en POO, le polymorphisme provient d’un typage d’ordre supérieur et des relations de sous-classement dans lequel les classes sont des ensembles polymorphiques de types (cf. le Polymorphisme F-Bound de William Cook et al. ou les articles de Anthony J.H. Simons sur la théorie de la classification dans le JOT, Journal Of Object Technology) ; les relations entre objets ne reposent plus sur le type des objets mais sur l’appartenance de leur type à des classes de types. (Ce que les développeurs Ruby appellent, in vulgare sermo, le «duke typing».) Les langages comme C++, Java ou C#, quant à eux, ont rejeté le concept de message, et réalisent par conséquent le polymorphisme par des relations de sous-typage (principe de Liskov) et par la surcharge de méthodes, mais celui-ci offre des possibilités en deçà de celui de la POO ; c’est pourquoi, pour étendre les possibilités polymorphiques, ils permettent, via la généricité, et dans une certaine mesure, un typage de second ordre (quantification universelle ou, au mieux, contrainte). Voici un exemple de type de second ordre contraint en Java :

public class Persistable<T extends Persistable<T>> {
  ...
}

Je conclurai brièvement cette article avec cette phrase du GOOS (Growing Object-Oriented Software) :

Your domain model is not in the classes you create in your source code, but in the messages that the objects pass to one another when they communicate at runtime.


comments powered by Disqus