Un switch annoté en Java
Un billet sur le blog de l’Agilitateur répondant à un billet sur le blog d’Excilys concernant les switchs en java me donne envie de parler un peu de codage en Java. Cet article est une variante du code C# proposé par Oaz en Java.
Je vais vous épargner toute la discussion sur le switch c'est bien / c'est nul, il y a déjà les commentaires enflammés sur le blog d'Excilys pour ça.
Passons directement au code : de la même manière qu'Oaz l'a fait, je dispose d'une interface Function qui définit le contrat que vont suivre les différents cas du switch. Pour que l'on sache que les différentes sous-classes sont bien des cases, on les annote avec un @Case créé pour l'occasion :
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Case {
String name();
}
On peut alors utiliser l'annotation de cette façon :
public interface Function {
void doYourJob();
}
@Case(name="bar")
public class BarFunction implements Function {
public void doYourJob() {
System.out.println("On va manger... des chips ! T'entends ? DES CHIPS !!");
}
}
@Case(name="foo")
public class FooFunction implements Function {
public void doYourJob() {
System.out.println("Hi, my name is foo!");
}
}
@Case(name="othercase")
public class OtherCase {
public void doYourJob() {
System.out.println("I'm not gonna be called :-(");
}
}
Reste maintenant le plus dur à faire : scanner un package à la recherche de classes annotées @Case et qui héritent de notre classe de base (ici Function, mais on va essayer de rester génériques).
La méthode scanCases va parcourir toutes les classes d'un package (grâce à la méthode getClasses() honteusement trouvée sur le forum de Dernière étape : la méthode on() qui renvoie l'instance associée à un cas ou jette une exception s'il n'existe pas. La glue entre tout ça, c'est un main assez simple :
Conclusion : c'est aussi possible en Java, avec un peu de gymnastique intellectuelle pour arriver à faire ce qu'on souhaite avec les generics :Ppublic class Switch<T> {
private String packageName;
private Map<String, T> cases;
private Class<T> parentType;
public Switch(String packageName, Class<T> parentType) {
this.packageName = packageName;
this.parentType = parentType;
scanCases();
}
private void scanCases() {
cases = new HashMap<String, T>();
try {
for (Class clazz : getClasses(packageName)) {
if (clazz.isAnnotationPresent(Case.class) && isA(clazz, parentType)) {
Case theCase = (Case) clazz.getAnnotation(Case.class);
cases.put(theCase.name(), (T) clazz.newInstance());
}
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Scanned " + cases.size() + " cases.");
}
private boolean isA(Class clazz, Class<T> class1) {
if (clazz.getSuperclass() == class1) {
return true;
}
for (Class intf : clazz.getInterfaces()) {
if (intf == class1) {
return true;
}
}
return false;
}
public T on(String caseName) throws IllegalArgumentException {
if (cases.containsKey(caseName)) {
return cases.get(caseName);
} else {
throw new IllegalArgumentException("The case " + caseName + " does not exist");
}
}
private List<Class> getClasses(String pckgname) { ... }
Sun Oracle). Pour chaque classe, on regarde si elle est annotée @Case et si elle étend ou implémente T. Si c'est le cas, on en ajoute une instance dans une Map avec le nom du cas comme clé.public class Main {
public static void main(String[] args) {
Switch<Function> swi = new Switch<Function>("com.jansen.annotatedswitch.demo", Function.class);
swi.on("foo").doYourJob();
swi.on("bar").doYourJob();
swi.on("othercase").doYourJob();
}
}