Pour l'essentiel, le pattern des interfaces dites "fluentes" consiste à proposer aux développeurs d'écrire du code dont le phrasé ressemble au langage naturel.
Par exemple ce phrasé fluide (exemple tiré du site de Martin Fowler):
TimeInterval meetingTime = fiveOClock.until(sixOClock);
exprime la même chose que cette version plus classique :
TimeInterval meetingTime = new TimeInterval(fiveOClock, sixOClock);
L'usage du mot 'until' (jusqu'à), courant dans la vraie vie, plutôt rare en programmation, fait la différence. De façon générale, les Fluent Interfaces privilégient l'emploient de méthodes au nom simples et issus du langage courant.
Deux exemples pour fixer les idées.
1. Rhino Mocks, framework open-source de mocks dynamiques développé par l'inévitable Ayende, permet d'écrire ça dans un test unitaire :
Expect.Call(myObject.Concat("a","b")).Return("ab");
Joli non ?
Si on portait cette Fluent Interface en français, ça donnerait quelque chose comme :
JeMAttendsACeQue.CetAppel(monObjet.Concat("a","b")).Retourne("ab");
2. ReadableRex, API qui encapsule la complexité des expressions régulières :
Regex socialSecurityNumberCheck = new Regex(Pattern.With.AtBeginning
.Digit.Repeat.Exactly(3)
.Literal("-").Repeat.Optional
.Digit.Repeat.Exactly(2)
.Literal("-").Repeat.Optional
.Digit.Repeat.Exactly(4)
.AtEnd);
Implémentation
Dans les grandes lignes, l'implémentation d'une Fluent Interface en elle-même n'a rien de sorcier et consiste souvent à retourner dans les méthodes et accesseurs le contexte en construction. Par exemple :
public class Configuration
{
string color;
int height;
Configuration Color(string color)
{
this.color = color;
return this;
}
Configuration Height(int height)
{
this.height = height;
return this;
}
}
Dans le détail, développer une Fluent Interface peut s'avérer extrémement complexe. La complexité tient à l'inconsistance fréquente du langage naturel. Qu'il s'agisse de l'anglais ou du français, la grammaire qui le définit regorge de contradictions, d'exceptions et d'effets de contexte.
Défi
Pour vous en convaincre, essayez d'implémenter une API fluente d'expression des nombres littéraux tel celui suggéré par Michael Feathers, et dont voici un aperçu des spécifications :
Assert.AreEqual(1, (int)FluentNumber.One);
Assert.AreEqual(23, (int)FluentNumber.Twenty.Three);
Assert.AreEqual(912, (int)FluentNumber.Nine.Hundred.And.Twelve);
Assert.AreEqual(777000, (int)FluentNumber.Seven.Hundred.And.Seventy.Seven.Thousand);
Ce que ne dit pas cet essemble de tests à faire réussir, c'est que l'autocompletion doit aussi fonctionner au mieux : taper 'Twenty' doit déclencher les propositions 'One', 'Two', ... 'Thousand" et 'Hundred', mais ni "Twenty" et autres dizaine, ni 'Thirteen' et autres 'teens'. C'est sûrement un excellent exercice pour appréhender les difficultés de développement d'une Fluent Interface.
Serez-vous capable de développer une API robuste vis-à-vis des tournures aberrantes telles que
FluentNumber.One.Thousand.One.Thousand
Un vrai travail de linguiste...
Liens :
Les commentaires récents