Une histoire de slots
Publié le mardi 04 mars 2014, 11:41 - modifié le 22/03/14 - Techniques de programmation - Lien permanent
- Article
- |
- Commentaires (0)
- |
- Annexes (0)
L’amélioration de la structuration du code d’un programme, avec la distinction des considérations techniques de celles métier, est le Saint Graal que poursuivent sans fins les développeurs. A cette fin, de nombreuses techniques ont fait leur apparition dont nous pouvons citer les traits ou les annotations. A côté de ceux-ci, il existe une technique élégante et uniforme que sont les slots. Mais, que sont ces derniers et en quoi peuvent ils nous aider dans notre quête ?
Un adage veut qu’un bon développeur soit un développeur paresseux. Ce qui se cache derrière cette paresse est l’aptitude du codeur à automatiser ses tâches redondantes et souvent ennuyeuses et de tout faire pour faciliter son boulot. L’avantage de cette attitude est évidemment pour l’organisation un gain de productivité à terme. En fait, à y regarder de près, le développeur ne fait que s’aligner aux lois de la physique : minimiser l’entropie. Un des aspects de cette caractéristique est, pour le programmeur, de minimiser la duplication de codes et, pour ce faire, il va user de techniques et de moyens qu’il connaît mais aussi qu’il a à sa disposition via l’outillage et surtout son langage de programmation. La structuration du code est l’une de ces techniques. Elle est vieille de plus de 30 ans mais n’a cessé et ne cesse de continuer à évoluer. L‘AOP (Aspect-Oriented Programming), les traits, les mixins, les closures, etc. ne sont que des aspects de celle-ci dans leur objectif de modulariser non seulement le code métier mais aussi et surtout le code transversal au métier et aux fonctionnalités du logiciel. Avec l‘AOP, nous pouvons définir des composants d’ordre technique comme la génération de traces ou la validation des autorisations d’appel d’opérations. Ces composants sont ensuite tissés aux objets métiers à l’exécution ou à la compilation ; l‘AOP permet de maintenir d’un côté les caractéristiques techniques et de l’autre les modules métiers sans les entacher de considération techniques. Les mixins et les traits sont deux concepts différents permettant de définir des propriétés, d’ordre technique ou fonctionnelle, transversales aux objets métiers puis de les associer à ces derniers de manière statique ou dynamique. Par exemple, l’ordonnancement ou encore la persistance peut être définie dans un mixin ou un trait, indépendamment des objets métiers des applications. Ces deux techniques ont été popularisés respectivement par les langages Ruby et Self. Les annotations, popularisés par Java, constituent une autre technique qui permet de qualifier sémantiquement des objets et d’attacher à celles-ci un comportement qui peut aller jusqu’à la génération de codes ; par exemple, des objets peuvent être annotés avec des marqueurs de gestion des transactions ou encore d’annotations métiers propres au domaine de l’application.
Pourtant, il existe un autre concept popularisé par Self en même temps que les traits : les slots. Ces derniers ne sont pas nouveaux puisqu’ils furent introduit par CLOS (Common Lisp Object System). (Dans CLOS comme dans Self, ils sont appelés descripteurs de slot). On retrouve ces derniers dans le langage Io et actuellement un travail est réalisé pour les intégrer à terme dans Pharo. Mais qu’est-ce qu’un slot ou descripteur de slot ? Dans le contexte d’un langage orienté-objet, un slot est la réification du point d’accès aux propriétés d’un objet ou, plus exactement, du mécanisme d’envoi/réception de messages (cf. mon billet sur les différentes POO). Tout accès aux propriétés d’un objet se fait par l’intermédiaire des slots qui, non seulement peuvent contrôler l’accès en lecture et en écriture de ces propriétés, mais aussi contenir des informations sémantiques sur celles-ci. Ils constituent une interface homogène d’accès aux propriétés de l’objet. Dans Self et Newspeak, le slot est un objet particulier qui agit comme une méthode retournant toujours une valeur (que celle-ci soit calculée dans le cas d’une méthode ou mémorisée dans le cas d’un attribut). Selon les langages de programmation, les slots peuvent n’être définis que pour l’accès aux champs d’un objet. Parce que c’est aussi un objet, le slot peut être manipulé comme n’importe quel autre objet du langage et, par voie de conséquence, il est possible alors de lui affecter aussi un comportement. Pour illustrer ceci, voici un petit exemple dans un pseudo-langage dans lequel le caractère point représente un slot (l’accès à un slot se fait par le point) :
Point.x := 3 Point.move := (abs, ord) { myself x += abs; myself y += ord; myself } Point.move.afterInvocation := () { View current updateView(myself) }
A la première ligne, le slot réfère un champs x
tandis qu’à la seconde ligne le slot réfère une opération move
. Le premier retournera la valeur 3 tandis que le second, une fois son action accomplie, retournera par défaut le point lui même. La dernière ligne spécifie quoi faire à la suite de l’invocation de l’opération : mettre à jour l’IHM dans laquelle est dessiné le point. L’attribut myself
des slots désigne l’instance de l’objet qui contient le slot. Dans la représentation ci-dessus, par homogénéité, les propriétés d’un slot (par exemple afterInvocation
) sont elles aussi des slots (dans ce cas, il existe un slot terminal qui référence ou stocke directement la valeur de l’attribut ou les instructions de la méthode). Nous aurions pu écrire le code comme suit pour plus de lisibilité :
Point setSlot($x, 3) Point setSlot($move, (abs, ord) { myself x += abs; myself y += ord; myself } ) Point.move setSlot($afterInvocation, () { View current updateView(myself) } )
dans lequel le signe $ désigne un symbole.
Parce qu’un slot peut représenter un point d’accès vers un autre objet, les relations inter-objets deviennent mécaniquement des citoyens de première classe au même titre que l’objet (ou la classe dans un langage orienté-classes). Pour illustrer ceci, imaginons un employeur qui a plusieurs employés et des employés qui est rattaché à un et un seul employeur :
Employer.employees := [] Employer.employees.onWrite := (e) { (e employer = myself) ifFalse( { e employer(myself) }) } Employee.employer := None(Employer) Employee.employer.onWrite : = (e) { (e employees contains(myself)) ifFalse( { e employees add(myself) }) }
Dans le code ci-dessus, chaque nouveau employé de l’employeur mettra automatiquement la référence employer
de l’employé avec son nouveau employeur. De même, chaque mise-à-jour de l’employeur de l’employé ajoutera ce dernier parmi les employés de son nouveau employeur :
anEmployee := Employee() anEmployer := Employer() anEmployer employees add(anEmployee) Assertion assertThat(anEmployee employer, is(anEmployer))
Les slots employees
et employer
représentent donc des relations qui peuvent être manipulés comme de simples objets et qui permettent de maintenir la cohérence des relations entre l’employeur et les employés. Nous pouvons alors introduire un mécanisme de création et de relations hiérarchiques de slots comme avec les objets (ou les classes d’objets) :
ManyToOneSlot := Slot { oppositeSlot: Slot, oppositeClass: Class, initialize: (anInstance, theOppositeSlot, theOppositeClass) { myself := anInstance oppositeClass := System class(theOppositeClass) oppositeSlot := oppositeClass slot(theOppositeSlot) }, onWrite: (anInstance) { ((oppositeSlot read: anInstance) = myself ) ifFalse( { oppositeSlot write(myself, anInstance) } ) } } OneToManySlot := Slot { oppositeSlot: Slot, oppositeClass: Class, initialize: (anInstance, theOppositeSlot, theOppositeClass) { myself := anInstance oppositeClass := System class(theOppositeClass) oppositeSlot := oppositeClass slot(theOppositeSlot) }, onWrite: (anInstance) { ((oppositeSlot read(anInstance)) contains(myself) ) ifFalse( { oppositeSlot write(myself, anInstance) } ) } } Employer := Object { employees: ManyToOneSlot(self, $employer, $Employee) } Employee := Object { employer: OneToManySlot(self, $employees, $Employer) }
De la même façon, il est alors possible de définir des slots dédiés à la persistance ou encore de combiner les slots entre eux pour réaliser des traitements transverses. Ceci et le fait d’ajouter du comportement aux slots ouvrent la porte à la méta-programmation, à la réflexion, sans commune mesure avec les autres concepts. Ce que permettent l’AOP, les annotations, certains usages des mixins et des traits, peuvent toutes être réalisés par les slots et ceci d’une manière uniforme et cohérente avec les concepts de base du langage.
Ce petit billet vous a donné un petite présentation du concept de slot, illustrée avec un pseudo-langage. J’espère qu’il vous aura donné un aperçu de la puissance et de l’expressivité qui se cache derrière ce simple concept.