IFT 232 Méthodes de conception orientée objets Séance d’exercices IV Date : 31 janvier et 7 février 2005 Objectifs Comprendre et maîtriser le design pattern « Factory Method » Comprendre et maîtriser le design pattern « Abstract Factory» Factory method design pattern Enoncé : Tout programme doit pouvoir rapporter les erreurs ou encore afficher des messages servant pour le déverminage. Soit l’interface suivante public interface Trace { // turn on and off debugging public void setDebug( boolean debug ); // write out a debug message public void debug( String message ); // write out an error message public void error( String message ); } Question 1 : Écrire une classe SystemTrace qui implémente Trace. Les instances de cette classe affiche le résultat dans la fenêtre de commande en ligne. public class SystemTrace implements Trace { private boolean debug; public void setDebug( boolean debug ) { this.debug = debug; } public void debug( String message ) { if( debug ) { // only print if debug is true System.out.println( "DEBUG: " + message ); } } public void error( String message ) { // always print out errors System.out.println( "ERROR: " + message ); } } Question 2 : Ajouter les instructions de déverminage nécessaires pour tracer sur la console chacun des appels de méthodes de votre solution de la séance d’exercices III. Dans la classe, il faut ajouter les instructions suivantes : //... some code ... SystemTrace log = new SystemTrace(); //... code ... log.debug( "entering loog" ); // ... etc ... Par contre dès que l’on voudra changer d’implémentation (par exemple, utiliser celle de la Q3), il faudra modifier cette classe. Question 3 : Écrire une classe FileTrace qui implémente Trace. Les instances de cette classe affiche le résultat dans un fichier. public class FileTrace implements Trace { private java.io.PrintWriter pw; private boolean debug; public FileTrace() throws java.io.IOException { // a real FileTrace would need to obtain the filename somewhere // for the example I'll hardcode it pw = new java.io.PrintWriter( new java.io.FileWriter( "c:\trace.log" ) ); } public void setDebug( boolean debug ) { this.debug = debug; } public void debug( String message ) { if( debug ) { // only print if debug is true pw.println( "DEBUG: " + message ); pw.flush(); } } public void error( String message ) { // always print out errors pw.println( "ERROR: " + message ); pw.flush(); } } Question 4 : Modifier votre réponse à la question 2 pour conserver la trace dans un fichier. Le nom de ce fichier sera spécifié dans les propriétés du programme et chargé dans une instance de ProgramProperties. Cette instance sera un Singleton créé à sa première utilisation. Cette classe sera sous-classe de java.util.Properties. Si vous n’êtes pas familier avec les alternatives permettant de déterminer les propriétés d’un programme, voir http://java.sun.com/docs/books/tutorial/essential/attributes/index.html public class ProgramProperties extends Properties { final static public String DEFAULT_FILE_NAME = "defaultProperties"; final static public String PROPS_FILE_NAME = "ex4.properties"; static private ProgramProperties instance__; static synchronized public ProgramProperties getInstance() { if (instance__ == null) { // create and load default properties ProgramProperties defaultProps = new ProgramProperties(); FileInputStream in; try { in = new FileInputStream(DEFAULT_FILE_NAME); defaultProps.load(in); in.close(); } catch (FileNotFoundException e2) { e2.printStackTrace(); } catch (IOException e3) { e3.printStackTrace(); } // create program properties with default ProgramProperties applicationProps = new ProgramProperties(defaultProps); try { // now load properties from last invocation in = new FileInputStream(PROPS_FILE_NAME); applicationProps.load(in); try { in.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } } catch (IOException e1) { e1.printStackTrace(); } instance__ = applicationProps;} return instance__; } private ProgramProperties(ProgramProperties defaultProps){ super(defaultProps); } private ProgramProperties() { super(); } } Question 5 : Vous avez probablement dû modifier votre programme en plusieurs endroits dans plusieurs classes. Afin de rassembler la création du système de trace en un seul endroit, écrivez une classe TraceFactory possédant une méthode « factory » abstraite getTrace(). Voir plus loin pour le code de la classe TraceFactory Question 6 : Ecrivez la classe SystemTraceFactory qui est une sous-classe de TraceFactory. Cette classe rend une instance de la classe SystemTrace. public class SystemTraceFactory extends TraceFactory { public Trace getTrace() { return new SystemTrace(); } } Question 7 : Ecrivez la classe FileTraceFactory qui est une sous-classe de TraceFactory Cette classe rend une instance de la classe FileTrace. import java.io.IOException; public class FileTraceFactory extends TraceFactory { public Trace getTrace() { try { return new FileTrace(); } catch (IOException e) { e.printStackTrace(); } // pour au mons render de quoi faire la trace return new SystemTrace(); } } Question 8 : Transformez TraceFactory en Singleton. La classe de l’instance sera spécifée dans le fichier de propriétés du programme. Par défaut, une instance de la classe SystemTraceFactory. public abstract class TraceFactory { static private TraceFactory instance__; private static TraceFactory createFactory() { if (ProgramProperties.getInstance().getProperty("trace.file") != null) return new FileTraceFactory(); return new SystemTraceFactory(); } static synchronized public TraceFactory getInstance() { if (instance__ == null) instance__ = createFactory(); return instance__; } public abstract Trace getTrace(); } Remarques http://java.sun.com/j2se/1.4.2/docs/guide/util/logging/ http://www.javaworld.com/javaworld/javaqa/2001-05/02-qa-0511-factory.html? Abstract Factory design pattern Enoncé : Vos clients sont américains et français. Bien entendu, les américains tiennent mordicus à travailler avec des mesures en système impérial alors que les français tiennent absolument au système métrique. Implémentez à l’aide du patron de conception « Abstract Factory» un programme qui garantisse aux utilisateurs de ne travailler que dans un seul des deux systèmes de mesure. Le système de mesure sera spécifié dans un fichier de propriétés. public class MeasureFactory { //un meilleur design serait celui défini aux questions précéentes public Measure getVolume(Number n, String u) { if (ProgramProperties.getInstance().getProperty("measure.system") == "us") return new MeasureUSImpl(n, u); return new MeasureMetricImpl(n,u); } public String getVolumeDefaultUnit(){return null;} public Measure getWeight(Number n, String u){return null;} public String getWeightDefaultUnit(){return null;} } public interface Measure { String getUnits(); Number getValue(); Measure add(Measure qty); Measure substract(Measure qty); } public interface MeasureMetric extends Measure { public static String MILLILITRES = "ml"; public static String LITRES = "l"; public static String MILLIGRAMMES = "mg"; public static String GRAMMES = "g"; } public class MeasureMetricImpl implements MeasureMetric { /** * @param n * @param u */ public MeasureMetricImpl(Number n, String u) { // TODO Auto-generated constructor stub } public String getUnits() { // TODO Auto-generated method stub return null; } public Number getValue() { // TODO Auto-generated method stub return null; } public Measure add(Measure qty) { // TODO Auto-generated method stub return null; } public Measure substract(Measure qty) { // TODO Auto-generated method stub return null; } }