Situation/problème:
Un objet doit définir plusieurs algorithmes interchangeables dynamiquement. Ces algorithmes bien qu'ils répondent à la même interface peuvent évoluer indépendamment.
Solution:
Le design Strategy répond à cette problématique par l'introduction de la composition dans la classe essentielle qui doit contenir l'algorithme.
Avantages:
Mise en place de bonne pratique,
Code extensible avec aisance d'inter changer les algorithmes,
Code facile à maintenir en cas d'évolution/changement.
Mise en pratique:
Notre exemple pratique, tiré d'une situation réelle, a pour but de comparer deux entreprises déclarées dans deux répertoires distincts
Par exemple, le premier est le répertoire FINESS et le second est SIRENE de l'INSEE.
Les déclarations peuvent complètement différer d'un répertoire à un autre sur :
Les formats des données,
Les codes de référence d'adresse ou de géolocalisation,
Bien d'autres indicateurs.
Exemple:
Pour fixer les idées prenons cet exemple:
La première entreprise a pour enseigne:
SARL AMBULANCE TAXI PARIS dans le premier répertoire disons par exemple le répertoire de l'INSEE.
et la seconde (qui peut être la même) est enregistrée encore dans un second répertoire qui gère spécialement le métier des ambulances médicales:
SARL TRANSPORT AMBULANCE PARIS
En résumé, la comparaison de ces deux entités va reposer sur un algorithme qui est interchangeable dynamiquement:
- Le premier algorithme, celui par défaut, qui compare l'égalité des enseignes (chaînes de caractères). Rarement utile. D'où le second
- Le second qui parcourt les mots qui composent chacune des enseignes afin de les comparer et puis établir des scores.
On a simplifié le problème en ne s'intéressant qu'à la notion de l'enseigne comme suit:
On affecte la note total de 100 lorsque les deux enseignes sont identiques (cad les deux chaînes de caractères sont identiques).
Sinon, on affecte une note liée à la comparaison de chaque mot qui compose ces enseignes
Les détails de cette comparaison n'est pas utile pour la compréhension du billet.
Les quatre classes sont de simples POJO utilisées pour la suite.
/****Classe EtabCommune***/
public class EtabCommune implements Serializable{ private static final long serialVersionUID = 1L; private String nom; public String getNom() { return nom; } public void setNom(String nom) { this.nom = nom; } public String toString(){ return "Nom:"+getNom(); } }
/****EtabFiness****/
public class EtabFiness extends EtabCommune { private int codeCategorie; public int getCodeCategorie() { return codeCategorie; } public void setCodeCategorie(int codeCategorie) { this.codeCategorie = codeCategorie; } public String toString(){ return "Finess:"+super.toString()+"\tCodeCategorie="+getCodeCategorie(); } }
/********EtabSirene************/
public class EtabSirene extends EtabCommune implements Serializable { private static final long serialVersionUID = 1L; int type; public int getType() { return type; } public void setType(int type) { this.type = type; } public String toString(){ return "Sirene:"+super.toString()+"\tType="+type; } }
/******NotesComparaison************/
public class NotesComparaison implements Serializable{ private static final long serialVersionUID = 1L; double noteEnseigne; public double getNoteEnseigne() { return noteEnseigne; } public void setNoteEnseigne(double noteEnseigne) { this.noteEnseigne = noteEnseigne; } public String toString() { return "noteEnseigne="+noteEnseigne;//+autres composantes de la note } }
Voici les algorithmes de comparaison avec implémentation par défaut (dans la class AbstractAlgorithme) et une seconde (pour démo).
Le client, un peu plus loin, va illustrer l'emploi des deux implémentations.
/*****Algorithme (interface,classe abstraite, implémentation )*****/
/**** IAlgorithme ***/
public interface IAlgorithme { public double calculateNoteEnseigne(String nomFiness,String nomSirene) ; }
/**** AbstractAlgorithme (alg utilisé par defaut)***/
public abstract class AbstractAlgorithme implements IAlgorithme { public double calculateNoteEnseigne(String nomFiness,String nomSirene) { if(nomFiness==null) return 0.0; if(nomFiness.equals(nomSirene))return 100.0; return 0.0; } }
/**** ConcreteAlgorithme: démo ***/
public class ConcreteAlgorithme extends AbstractAlgorithme { public double calculateNoteEnseigne(String nomFiness,String nomSirene) { if(nomFiness==null || nomFiness.trim().equals("") || nomSirene==null || nomSirene.trim().equals(""))return 0.0; final String[] listeFinessEnseigne=nomFiness.split(" "); final String[] listeSireneEnseigne=nomSirene.split(" "); double[] notes =new double[listeFinessEnseigne.length]; int dimension=notes.length; for(int i=0;i<dimension;i++) notes[i]=0.0; int k=0; for( String mot:listeFinessEnseigne){ for (String terme: listeSireneEnseigne ){ if (mot.equals(terme)) notes[k]=100.0; } k++; } double note=0.0; for(double n:notes){ note+=n; } return note/dimension; //DIV ZERO! } }
La classe Comparaison contient les détails de l'implémentation du motif stratégie.
En effet, la classe Comparaison délègue l'algorithmique de comparaison à la classe/interfae AbstractAlgorithme/IAlgorithme via l'attribut algorithme.
Néanmoins, cette classe fournit, dans le constructeur sans argument, une implémentation par défaut.
Le client peut redéfinir dynamiquement l'algorithme de comparaison, comme illustré dans le client ci-après
/****Comparaison (interface et implémentation)***/
public interface IComparaison { public NotesComparaison compare(EtabFiness finess, EtabSirene sirene); public void setAlgorithme(IAlgorithme algorithme) ; }
/************ classe importante implémentant le motif stratégie************************/
public class Comparaison implements IComparaison{ private IAlgorithme algorithme; public Comparaison(IAlgorithme algorithme){ this.algorithme=algorithme; } public Comparaison(){ this.algorithme=new Algorithme(); } /***Methode prinicpale...***/ public NotesComparaison compare(EtabFiness finess,EtabSirene sirene){ NotesComparaison notesComparaison=new NotesComparaison(); /*****noteEnseigne à claculer...****/ double noteEnseigne=0.0; noteEnseigne=algorithme.calculateNoteEnseigne(finess.getNom(), sirene.getNom()); notesComparaison.setNoteEnseigne(noteEnseigne); return notesComparaison; } //setters... public void setAlgorithme(IAlgorithme algorithme) { this.algorithme = algorithme; } //inner class private class Algorithme extends AbstractAlgorithme{ } }
Avant de présenter le client, voici le fichier de configuration spring qui va être utilisé:
/**** spring.xml****/
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" > <bean id="finess1" class="fr.netapsys.designpatterns.entites.EtabFiness"> <property name="nom" value="SARL AMBULANCE TAXI PARIS"/> <property name="codeCategorie" value="100"/> </bean> <bean id="sirene1" class="fr.netapsys.designpatterns.entites.EtabSirene"> <property name="nom" value="SARL TRANSPORT AMBULANCE PARIS"/> <property name="type" value="1"/> </bean> </beans>
/****************** Main client**************/
public class Main { final private static Logger logger=Logger.getLogger("fr.netapsys.designpatterns.main.Main.class"); public static void main(String[] args) { /* point d entree de spring: déclarer dans le fichier spring.xml le bean */ ApplicationContext ctx=(ApplicationContext) new ClassPathXmlApplicationContext(new String[]{"resources/spring.xml"}); //Exemple de finess structure ... EtabFiness finess1 = defineExempleEtabFiness(ctx); //exemple de sirene structure ...************************** EtabSirene sirene1 = defineExempleEtabSirene(ctx); /********************** TEST 1: Comparaison par defaut (cad algorithme par defaut qui opere) ***************/ IComparaison comparaison=new Comparaison(); NotesComparaison notesComparaison=comparaison.compare(finess1, sirene1); logger.info("Résultat de comparaison par defaut de "+finess1+" et de "+sirene1 + " -->NOTES:"+notesComparaison+" " ); /********************** TEST 2: attache un algorithme concret à la comparaison ***************/ //definir algo de la comparaison IAlgorithme algorithme = defineConcreteAlgorithme(); //attacher cet algo à Comparaison //comparaison=new Comparaison(algorithme); comparaison.setAlgorithme(algorithme); notesComparaison=comparaison.compare(finess1, sirene1); logger.info("Resultat de comparaison avec algorithme concret de "+finess1+" et de "+sirene1 + " -->NOTES:"+notesComparaison ); } private static IAlgorithme defineConcreteAlgorithme() { IAlgorithme algorithme=new ConcreteAlgorithme(); return algorithme; } private static EtabSirene defineExempleEtabSirene(ApplicationContext ctx) { return (EtabSirene)ctx.getBean("sirene1"); } private static EtabFiness defineExempleEtabFiness(ApplicationContext ctx) { return (EtabFiness)ctx.getBean("finess1"); } }
L'exécution de la classe Main produit la sortie suivante:
INFO ( Main.java:54) Résultat de comparaison par defaut de Finess:Nom:SARL AMBULANCE TAXI PARIS CodeCategorie=100 et de Sirene:Nom:SARL TRANSPORT AMBULANCE PARIS Type=1 -->NOTES:noteEnseigne=0.0 INFO ( Main.java:64) Resultat de comparaison avec algorithme concret de Finess:Nom:SARL AMBULANCE TAXI PARIS CodeCategorie=100 et de Sirene:Nom:SARL TRANSPORT AMBULANCE PARIS Type=1 -->NOTES:noteEnseigne=75.0
Voilà, ça fait beaucoup de lignes de codes mais le résultat est intéressant puisque changer/redéfinir l'algorithme est dynamique.
Vous pouvez en ajouter/définir un autre adapté aux nouvelles règles métier sans modifier la classe Comparaison.
Commentaires