Archives pour la catégorie Design patterns

Implémentation des principaux design patterns en Java

Pattern « Visiteur » et généricité de méthodes

Petit exemple d’implémentation de filtrage et transformation de collection dans lequel on implémente le patterne « Visiteur ».

L’intérêt est de créer un code qui est réutilisable, facile a maintenir et générique.

Dans l’exemple suivant on va implémenter une classe « CollectionUtils » avec son interface « ICollectionUtils ». Son rôle va être celui d’un service qui met a disposition des « méthodes génériques » pour effectuer des opérations sur des listes quelque soit le type des paramètres d’entrée et des paramètres de sortie. L’intérêt de rendre les méthodes génériques et non pas classe réside dans le fait qu’on utilisera la même instance d’objet pour traiter des collections de type différents. On aurait aussi pu faire des « classes génériques », mais ça aurait impliqué de créer une instance par type d’objets à traiter.

Notre service va donc proposer 3 méthodes :
filter : prend en paramètre une liste générique et un IEntryFilter qui sera notre implémentation de filtre qui s’appliquera sur chaque élément de la liste.
listToMap : prend en paramètre une liste générique et un IEntryMapper qui sera notre implémentation de mapper qui transformera nos éléments en un objet de type clé / valeur.
transform : prend en paramètre une liste générique et un IEntryTransformer qui sera notre implémentation pour transformer un élément d’un type en un autre.

Voici l’implémentation de notre service et de sont interface :

import java.util.List;
import java.util.Map;

/**
 * Filtrage et transformation des noeuds JCR
 *
 */
public interface ICollectionUtilsService {
    /**
     * Methode de filtrage des noeuds d'une liste
     * @param nodeList List<Node>
     * @param filter List<Node>
     * @return List<Node>
     */
    public <U> List<U> filter(List<U> nodeList, IEntryFilter<U> filter);

    /**
     * Methode de transformation d'une List en Map
     * @param nodeList List<Node>
     * @return <T>
     * @throws Exception
     */
    public <T,K,V> Map<K,V> listToMap(List<T> nodeList, IEntryMapper<T, K, V> mapper) throws Exception;

    /**
     * Permet la transformation d'une liste en une autre
     * @param list List<T1>
     * @return List<T2>
     */
    public <T1, T2> List<T2> transform(List<T1> list, IEntryTransformer<T1, T2> transformer) throws Exception;
}

L’implémentation qui correspond :

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import ICollectionUtilsService;
import IEntryMapper;
import IEntryTransformer;
import IFilter;

/**
 * Filtrage et transformation des noeuds JCR
 *
 */
public class CollectionUtilsService implements ICollectionUtilsService {

    @Override
    public <U> List<U> filter(List<U> list, IFilter<U> filter) {
        List<U> newList = new ArrayList<U>();

        for (U item : list) {
            if (filter.accept(item)) {
                newList.add(item);
            }
        }

        return newList;
    }

    @Override
    public <T, K, V> Map<K, V> listToMap(List<T> list, IEntryMapper<T, K, V> mapper) throws Exception {
        Map<K,V> map = new HashMap<K, V>();

        for (T item : list) {
            map.put(mapper.getKey(item), mapper.getValue(item));
        }

        return map;
    }

    @Override
    public <T1, T2> List<T2> transform(List<T1> list, IEntryTransformer<T1, T2> transformer) throws Exception {
        List<T2> outputList = new ArrayList<T2>();

        for (T1 item : list) {
            outputList.add(transformer.transform(item));
        }

        return outputList;
    }

}

L’interface IEntryFilter va définir une méthode « accept ». L’implémentation de celle-ci va permettre, en fonction du type d’objet, de définir si pour un élément de la liste le filtre accepte l’élément pour le laisser dans la liste ou bien si il est rejeté.

public interface IEntryFilter<T> {
    /**
     * Accepte un noeud si il correspond au pattern
     * @param node Node
     * @return boolean
     */
    public boolean accept(T element);
}

L’interface IEntryMapper va définir deux méthodes : getKey et getValue qui vont, depuis un élément, renvoyer une clé ou une valeur pour pouvoir créer notre Map de sortie.

public interface IEntryMapper<T, K, V> {
    /**
     * Renvoit la clé
     * @return <K>
     */
    public K getKey(T element) throws Exception;

    /**
     * Renvoit la valeur
     * @return <V>
     */
    public V getValue(T element) throws Exception;
}

L’interface IEntryTransformer va définir une méthode « transform » qui va prendre un élément de notre liste initiale et le transformer dans un type correspondant à la liste de sortie.

public interface IEntryTransformer<T1, T2> {
    /**
     * Prend un element d'un type et le transforme dans un autre type (potentiellement le même)
     * @param item T1
     * @return T2
     * @throws Exception
     */
    public T2 transform(T1 item) throws Exception;
}

Avec ces quelques classes nous allons pouvoir effectuer beaucoup d’opérations sur des listes de types différents. L’intérêt étant que si l’on doit appliquer un nouveau filtre ou faire une nouvelle transformation sur des éléments, nous n’aurons plus à recopier du code inutile mais nous pourrons juste implémenter un nouveau IEntryFilter ou un IEntryTransformer spécialisés et les utiliser de la même manière. Voici des exemples d’implémentation de ces interfaces :

JCRNodesByTypeFilter : Filtre une liste de noeuds JCR en fonction du type de noeud :

public class JCRNodesByTypeFilter implements IEntryFilter<Node> {
    /**
     * Le logger de la classe
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(JCRNodesByTypeFilter.class);

    /**
     * Constructeur
     */
    public JCRNodesByTypeFilter(String type){
        this.nodeType = type;
    }

    /**
     * Type de noeud recherché
     */
    private String nodeType = null;

    /* (non-Javadoc)
     * @see IJCRNodeFilter#Accept(javax.jcr.Node)
     */
    @Override
    public boolean accept(Node noeud) {
        boolean retVal = false;
        Node tmpNode = null;

        try {
            if (noeud.getPrimaryNodeType().isNodeType(nodeType)) {
                // Si le type correspond, on accepte le noeud dans la liste de retour
                retVal = true;
            }

        } catch (RepositoryException e) {
            LOGGER.error("JCRSpecificTypesNodeFilter : Impossible de déterminer le type du noeud.");
        }

        return retVal;
    }
}

JCRPublishedNodeTypeFilter : Filtre une liste de noeuds JCR pour ne garder seulement les noeuds au statut « publié » :

public class JCRPublishedNodeTypesFilter implements IEntryFilter<Node> {
    /**
     * Le logger de la classe
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(JCRPublishedNodeTypesFilter.class);

    /**
     * Constructeur
     */
    public JCRPublishedNodeTypesFilter(){
    }

    /* (non-Javadoc)
     * @see IJCRNodeFilter#Accept(javax.jcr.Node)
     */
    @Override
    public boolean accept(Node noeud) {
        boolean retVal = false;

        try {
            // On recupere que les noeuds publiés
            if ("published".equals(noeud.getProperty("publication:currentState").getString())) {
                retVal = true;
            }

        } catch (RepositoryException e) {
            LOGGER.error("JCRSpecificTypesNodeFilter : Impossible de récupérer la propriété du noeud.");
        }

        return retVal;
    }
}

Pour les « transformer » et les « mapper » c’est la même chose. On crée un nouvelle implémentation qui fait un traitment spécifique voulu et qui l’applique à tous les éléments de notre collection.

Côté utilisation de notre service, nous avons si dessous un exemple d’une méthode qui va exécuter un « transformer » puis un « filtre » sur notre collection initiale. Exemple d’utilisation de notre « CollectionUtils » :

/**
 * Construction de la nouvelle Liste avec la methode filter
 *
 */
List<Node> listeTransformee = service.transform(listeNoeuds, new EntryTransformerFrozenNodeToNode());
return service.filter(listeTransformee, new JCRNodesByTypeFilter(typeContenu));

Enjoy !

Publicités