points de vue

les déambulations d'un codeur

Aller au contenu | Aller au menu | Aller à la recherche

Une histoire de slots

card slotL’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.

Miguel Moquillon

Auteur: Miguel Moquillon

Restez au courant de l'actualité et abonnez-vous au Flux RSS de cette catégorie

Commentaires (0)

Soyez le premier à réagir sur cet article

Ajouter un commentaire Fil des commentaires de ce billet

no attachment



À voir également

Exemple d'immutabilité en Java

Dans un billet précédent sur l’égalité d’identité et celle de valeurs, je vous ai parlé d’objets immuables pour lesquels l’égalité de valeur et l’égalité d’identité se confondent. J’ai souvent vu dans divers blogues sur l’immutabilité en Java l’utilisation du mot clé final. J’ai toujours trouvé son usage pour réaliser l’immutabilité comme absurde et surtout par trop contraignant. Pour moi, il ne sert à rien de qualifier les propriétés des objets comme final étant donné que celles-ci doivent être encapsulées selon les principes de la programmation orienté objet. Non, l’immutabilité des objets devrait au contraire se faire au niveau du comportement et surtout des mutateurs de ces objets.

Lire la suite

Une histoire d'objets obèses ...

Il arrive dans un projet en Java de se trouver, selon le métier ou le domaine adressé, avec des classes d’objets obèses en méthodes qu’elles soient publics ou propres aux objets de la classe. Or, sachant que l’on passe plus de temps à lire, voir à toucher du code existant qu’à en écrire de nouveaux, ceci peut vite devenir pénible. Evidemment, avec nos IDE actuels, il est facile de naviguer entre les différentes méthodes et propriétés d’une classe. Mais en général ceci signifie que l’on sait, déjà, à peu près ce que l’on cherche ou que l’on connait a minima les responsabilités ou certaines particularités d’implémentation de la classe. Lorsqu’on doit toucher du code inconnu ou au mieux revenir sur du code au bout de 6 mois, nous aimons bien identifier aisément les parties à utiliser ou à retoucher et accéder à l’essentiel sans se perdre dans les méandres de la ou des classes inspectées. En effet, il peut être difficile, avec de telles classes, de démêler le comportement de l’objet, ce qui le caractérise, du reste. En tout cas c’est mon cas. Pour éviter de tels embonpoints, je vous propose d’utiliser les approches de certains langages fonctionnels comme Haskell (ou OCaml), dans lesquels les types et les fonctions sur ces types sont séparés (au sein d’un même module tout de même).

Lire la suite