jeudi 10 octobre 2013

La généricité en Java : utilisation particulière

Les génériques en Java permettent une grande souplesses dans le code.

Il peut très intéressant de tirer partie de cette généricité pour par exemple, automtiser la création d'objet.

Si une classe attend un objet en entrée et retourne un autre objet, il peut être pratique d'automatisé la création d'objet.

Voici un exemple de classe :
import org.apache.commons.beanutils.ConstructorUtils;
import java.lang.reflect.ParameterizedType;

public abstract class Services<Q extends Query, R extends Response> {
    private Q q ;
    private R r ;

    public Services() {
        q = (Q) buildGeneric(getClass(), 0) ;
        r = (R) buildGeneric(getClass(), 1) ;
    }
    
    public abstract void execute() ;
    
    public Q getQuery() {
        return q ;
    }

    public R getResponse() {
        return r ;
    }

    private static Object build(Class<?> clazz) {
        Object result = null;
        try {
            result = ConstructorUtils.invokeConstructor(clazz, null);
        } catch (Exception e) {
            // Nothing
        }
        return result;
    }

    private static Object buildGeneric(Class<?> clazz, int position) {
        return build(getClassGeneric(clazz, position));
    }

    private static Class<?> getClassGeneric(Class<?> classGeneric, int position) {
        return (Class<?>) ((ParameterizedType) classGeneric.getGenericSuperclass()).getActualTypeArguments()[position];
    }    
}

Cette classe Services créé automatiquement les objets de requête et de réponse en récupérant dans la classe concrète les paramètres de générique.
Ces objets peuvent être de n'importe quel type.

Le constructeur récupère le nom de la classe des deux génériques et créer les objets (en utilisant une classe externe ConstructorUtils de common-beanutil pour plus de facilité).

Chaque argument générique est attaché à la classe lors de la compilation.
Cela signifie qu'il faut créer une classe à la main.
Il n'est pas possible de créer une classe anonyme à l'éxécution.

Voici une classe héritant de la classe Services :
public class MonService extends Services<MaQuery, MaResponse> {
    @Override
    public void execute() {
        System.out.println("TestSevice : execute()");
        
    }
}

Les deux classes de requête et de réponse :
public class MaQuery {
    @Override
    public String toString() {
        return "MaQuery" ;
    }
}

public class MaResponse {
    @Override
    public String toString() {
        return "MaResponse" ;
    }

}

Maintenant, une classe de test :
public class TestService {
    public static void main(String[] args) {
        Services s = new MonService() ;
        
        s.execute() ;
        
        System.out.println(s.getQuery().toString()) ;
        System.out.println(s.getResponse().toString()) ;
    }
}

Et voilà. Cela permet un code simple, générique, clair.
Il pourrait être ajouter des contrôles commun sur les objets requête et réponse.

Les deux défauts, comme dit précédemment, il faut créer une classe à la main pour qu'elle soit compilée afin d'avoir les informations de généricité (nom classe requête et réponse) via les meta-data.
La performance. Avec ce système, la réflexion est mise en jeu et coûte cher en temps d'exécution.

Toutefois, cette méthode est souvent utilisée. Pour pallier le problème de performance, d'autre mécanisme sont mis en place (cache...)

Aucun commentaire:

Enregistrer un commentaire