Ca fait quelque temps que j’utilise des frameworks avec une interface fluide (cf Martin Fowler Fluent Interface). Je pense à Mockito, JMock2 et Hibernate Query API entre autres. Depuis peu j’utilise fréquemment le Test Data Builder Pattern. Ces derniers sont plutôt des Domain Specific Languages, une version évoluée des interfaces fluides. Mais je n’avais jamais réalisé que ce soit un jeu d’enfant de créer sa propre interface fluide. Prenons par exemple
Math.pow(3, 2)
Ca fait quoi au juste? Ah oui c’est l’exposant 3 de 2. Ou peut-être l’exposant 2 de 3? Et si on écrivait plutôt
exponent(3).of(2)
Ici on voit bien ce qui se passe! Certes la javadoc de Math.pow(double a, double b) est clair et ça marche bien quant on écrit du code, mais pas quand on lit du code déjà écrit. Puisqu’on estime que du code est lu 10-100 fois plus qu’il n’est écrit (sans parler d’un framework open source), il est très important de rendre la vie facile à nos lecteurs.
Mais alors qu’est-ce ça demande comme effort de transformer Math.pow(a, b) en exponent(a).of(b)? Très peu en fait :
class Exponent {
private final double exponent;
public Exponent(double exponent) {
this.exponent = exponent;
}
public static Exponent exponent(double exponent) {
return new Exponent(exponent);
}
public double of(double base) {
return Math.pow(base,exponent);
}
}
puis un import statique dans la classe client
import static Exponent.exponent;
L’import statique avec le Factory Method exponent() jouent un rôle centrale, ils évitent le bruit syntaxique qu’il y dans
new Exponent(3).of(2)
//ou
Exponent.exponent(3).of(2)
J’ai pensé à cette astuce lorsque j’étais entrain de travailler sur un défi TDD – faire un convertisseur du système décimal vers un système quelconque. Dans ma solution il y avait cette fonction qui sert à calculer le chiffre à une position donnée dans le nouveau système. Ainsi si je veux transformer 3 en système binaire, pour connaitre si j’ai le chiffre à la position 0 (le plus à droite) je fais (3 modulo 2^1) quotient 2^0. Et pour la position 1 soit la plus à droite (3 modulo 2^2) quotient 2^1.
J’étais moyennement content de ça
private String digitAtPosition(int pos, int tenBaseNumber) {
int nextPos = pos+1;
long remainderOfNextPos = tenBaseNumber % nthPowerOfBase(nextPos);
long digitInTenBase = remainderOfNextPos / nthPowerOfBase(pos);
return convertSingleDigit(digitInTenBase);
}
protected final long nthPowerOfBase(int n) {
return round(pow(base,n));
}
on lit plus facilement
private String digitAtPosition(int pos, int tenBaseNumber) {
int nextPos = pos+1;
long remainderOfNextPos = tenBaseNumber % exponent(nextPos).of(base);
long digitInTenBase = remainderOfNextPos / exponent(pos).of(base);
return convertSingleDigit(digitInTenBase);
}
De plus cette classe extraite, Exponent, n’a rien de spécifique à mon convertisseur et pourrait potentiellement être utilisée ailleurs, promouvant ainsi la réutilisation. De plus et de manière générale une interface fluide est le début d’un DSL que l’on verrait plus facilement apparaitre à des parties du code fortement utilisés.
Et la performance?
- Make it work
- Make it good
- (Then perhaps) Make it fast
Et si le besoin de rapidité apparait, il y a plusieurs façons d’attaquer le problème, revenir en arrière, ou introduire un cache des objets crées, ou en modifiant l’interface un poil :
exponent(3, of(2))
...
public static double of(double number) {
return number;
}
Des variantes de la classe Exponent
class Exponent {
double exp;
public static Exponent exponent(final double exponent) {
return new Exponent() {{
this.exp = exponent;
}};
}
public double of(double base) {
return Math.pow(base,exp);
}
}
encore une autre qui évite la variable d’instance et qui permet d’autres implementations de of() – par exemple log(logBase).of(someNumber).
class FluidMaths {
public static Exponent exponent(final int exponent) {
return new Exponent() {
public Long of(int base) {
return round(pow(base,exponent));
}
};
}
interface Exponent {
public Long of(int base);
}
}