août
2008
Dans ce quatrième volet de la série « Introduction à Scala », je vais présenter la notion des traits dans Scala, qui sont ce qui ressemble le plus aux interfaces de Java.
1. Présentation
La définition d’un trait en Scala se présente comme suit:
Ici, ce qui est entre [ ] dénote un élément optionnel, le | indique que l’une ou l’autre des alternative est applicable, et … indique que ce qui précède peut être répété à volonté.
Bref, le plus important c’est le mot clé « trait« , qui la différencie d’une classe ordinaire.
Voici un exemple d’un trait:
Ici, j’ai défini un trait nommé Action qui contient une seule méthode abstraite action.
Voici maintenant une classe qui étend ce trait :
Veuillez noter qu’une classe peut étendre plusieurs traits, et ce en utilisant le mot clé with à partir du second tait étendu, comme montré dans l’exemple suivant:
2. Les traits comme des interfaces
Dans une première approche, on peut utiliser les traits de Scala pour émuler les interfaces de Java, en limitant leur rôle à la définition des contrats des classes.
A titre d’exemple, considérons ce bout de code :
J’ai commencé par définir un trait Age pour représenter une entité ayant un âge, d’où la méthode abstraite age.
J’ai ensuite défini un trait Comparable qui contient quelques méthodes permettant de comparer deux entités ayant un âge comme sameAge, olderThan, etc.
A la fin, j’ai implémenté ces deux traits dans une classe Person.
3. Les traits pour simplifier l’implémentation
La tache des classes implémentant le trait Comparable peut être grandement simplifié quand on sait que plusieurs méthodes du trait Comparable peuvent être déduites de deux autres méthodes de ce même trait.
Par exemple, si on suppose disposer de sameAge et olderThan, on peut donc déduire le reste des méthodes :
– youngerThan est alors !olderThan(that) && !sameAge(that)
– etc.
Les traits Scala, et à l’inverse des interfaces de Java, peuvent avoir des champs et implémenter des méthodes.
En tirant profit des deux points précédents, on peut ré-écrire l’exemple précédent en :
Ainsi, il suffit d’implémenter 2 méthodes des 5 définies par Comparable pour avoir une implémentation complète.
4. Utiliser les traits comme décorateurs
Un autre utilisation possible des itérateurs serait des les combiner à une classe donnée en tant que décorateurs, apportant de nouvelles fonctionnalités ou modifiant les fonctionnalités existantes.
Pour illustrer ce concept, commençons par définir un trait représentant une opération quelconque :
Ainsi qu’une implémentation bidon, qui lance ou non, et de façon aléatoire une exception :
Je vais commencer par un décorateur simple, qui ne fait qu’afficher un message avant l’exécution de l’action concrète et un autre après sa fin.
Un tel décorateur se définit comme un trait ordinaire, avec quelques points à respecter :
– Il doit hériter du trait définissant l’action (Action)
– Il doit implémenter la méthode du trait parent, tout en déclarant cette implémentation comme abstraite, pour s’assurer que la classe d’implémentation concrète définisse bel et bien l’action réelle.
– L’implémentation réelle de l’action peut alors être référencée par super.action()
dans le trait de décoration.
En suivant ces règles, le trait de logging s’écrit alors :
Maintenant, pour combiner ce décorateur à l’action concrète, on procède comme suit :
Notez l’instanciation de l’action concrète suivie du mot clé with et du nom de du trait décorateur.
A l’exécution, on obtient :
TestAction
after action
On peut dès lors imaginer toute sorte de décorateurs, comme par exemple un décorateur qui répète N fois l’action tant qu’elle lance une exception :
Ou encore un décorateur qui rend l’exécution synchronisée :
Enfin, on peut associer une classe donnée à plusieurs traits de décoration comme suit :
Notez que l’ordre de l’apparence d’un trait dans l’instanciation influe sur son ordre à l’exécution : le premier trait qui apparaît serait le dernier à être exécuté, et ainsi de suite.
Dans tous les cas de figure, la classe concrète serait exécutée en dernier.
Ainsi, dans l’exemple précédent, l’ordre d’exécution serait :
2. LoggedAction
3. SynchronizedAction
4. TestAction.
Conclusion
Dans ce volet de l’introduction à Scala, j’ai présenté les traits via quelques cas d’utilisations courants.
N’hésitez surtout pas à me communiquer vos impressions, critiques et propositions et ce en commentant dans ce même blog, ou via messages privés (via forum).
@trungsi (spoiler)
Tu peux imbriquer les classes et les traits pour construire des espaces de nommage hiérarchisés.
Les membres peuvent être des méthodes, des champs, mais aussi des types abstraits.
Tu peux mettre des contraintes sur ces types abstraits.
Avec l’héritage tu peux spécialiser (renforcer la contrainte ou covariance) ou généraliser (relâcher la contrainte ou contravariance) les types abstraits.
Tu peux construire un nouveau trait ou une nouvelle classe abstraite en le/la paramétrant par ses types abstraits.
Au final tu bénéficies de:
* toute la sécurité d’un langage statique comme Java
* toute la flexibilité d’un langage dynamique comme Groovy
Oui c’est intéressant, comme doc je n’ai qu’un vieux ScalaByExemple.pdf et il n’est vraiment pas bavard sur les traits.
@Ricky81
Je ne crois pas qu’il soit vraiement indispensable ce with…super, de toute façon tu peux toujours prendre une fonction en argument et en renvoyer une nouvelle version décorée avec ce que tu veux.
Par contre, pour faire de la programmation orienté aspect, je conçois que ça puisse être un sucre syntaxique qui a son utilité.
Il y a bien du sucre syntaxique pour les monades, alors pourquoi pas pour l’AOP qui est quand même plus populaire.
Merci pour les intros à Scala.
J’essaie de m’intéresser à ce langage. Mais je dois avouer que c’est vraiment pas facile :(. Alors qu’avec Groovy par exemple, le « learning curve » est très faible.
J’attends donc avec impatience un billet qui montre la puissance (supériorité) de Scala par rapport à d’autres langages surtout avec des langages comme Groovy, JRuby,…
Tiens, on sent une très vague ressemblance avec les traits du C++ (j’ai écrit un article sur le sujet).
C’est le même principe mais appliqué pour du polymorphisme dynamique.
Dans tous les cas, billet très intéressant, merci.
Ricky> De quoi concevoir de l’héritage multiple propre donc
C’est le but justement quoi que d’autres diront que c’est plutôt de l’héritage multiple bridé et à la sournoise
Très intéressant comme introduction aux traits.
J’ai quand même eu du mal au début dans l’utilisation du mot clé super qui ne porte pas vraiment sur le concept d’héritage mais sur la déclaration « with ».
De quoi concevoir de l’héritage multiple propre donc