Introduction▲
De nombreuses méthodes existent pour simplifier la phase de conception des logiciels. Parmi les plus connues, considérons Merise et UML. Mais une autre méthode existe, plus proche de l'implémentation. Lors de la conception d'une application, de nombreux problèmes peuvent survenir. Le système des Design Patterns, ou motifs de conception, représente un système objet destiné à la résolution de problèmes techniques.
Un design pattern constitue un petit ensemble de classes apte à offrir la solution la plus efficace à un problème. La définition d'un motif de conception repose donc sur trois critères. Premièrement, le problème à résoudre est classique et bien connu. Ensuite, l'ensemble des classes employées porte un nom unique (on parle par exemple du motif « Decorator »). Enfin, la solution que propose ce motif correspond à la résolution la plus optimale du problème.
Les designs patterns proviennent du travail de nombreux développeurs qui se sont tour à tour penchés sur les mêmes problèmes. En mettant en corrélation ces travaux, on a pu désigner les meilleures solutions découvertes sous le terme de motifs de conception. La connaissance de ces motifs permet au programmeur de trouver rapidement des implémentations pour ses programmes.
La principale difficulté réside dans l'identification du problème et dans sa mise en relation avec des motifs connus.
I. Exemples de motifs▲
L'ouvrage de référence sur les motifs de conception se nomme « Design Patterns », de Gamma, Helm, Johnson et Vlissides. Ces auteurs sont plus communément cités sous le nom du Gang Of Four (GOF). Dans cette œuvre, 23 motifs de conception se voient détaillés et analysés. En voici quelques-uns :
- l'association modèle-vue-contrôleur (MVC), qui dissocie le traitement des données de leur représentation graphique au sein des interfaces utilisateurs. Ce motif est employé à outrance au sein de l'API Java Swing ;
- l'observateur permet à un objet de recevoir des notifications lorsque l'objet observé subit des modifications. Ici encore l'API Swing contient de nombreux exemples d'utilisation de ce motif ;
- l'itérateur sert à parcourir une liste d'objets homogènes ;
- la fabrique qui instancie des objets depuis une classe particulière. Ce motif s'emploie souvent lorsque les constructeurs ne sont pas publics. Plusieurs exemples se retrouvent dans le JDK sous les noms de *Factory.
Les motifs de conception connus se groupent en trois catégories. La première incarne les motifs de création qui réalisent des instances de classes pour vous. Le second groupe concerne les motifs structuraux servant à composer de larges structures d'objets (comme les interfaces graphiques). Enfin la troisième catégorie regroupe les motifs de comportement aidant à la communication entre les objets du système.
Ces cours se focaliseront chacun sur un motif bien particulier avec une mise en application. De la sorte vous pourrez reprendre les concepts étudiés pour vos propres applications. Les langages utilisés seront Java et Python, mais vous pourrez sans aucun problème adapter les motifs à d'autres langages. L'ouvrage du Gang Of Four se basait sur les langages-objet C++ et Small Talk.
II. Les motifs de création▲
La catégorie des motifs de création regroupe cinq motifs distincts. Dorénavant nous emploierons la désignation anglophone des motifs. Les cinq motifs de ce groupe sont donc les suivants :
- le motif Factory ;
- le motif Abstract Factory ;
- le motif Builder ;
- le motif Prototype ;
- le motif Singleton.
II-A. Application exemple▲
Notre étude des motifs se limitera à l'apprentissage du motif Factory, ou fabrique. En utilisant le motif de conception Factory nous allons créer une application pouvant lire des données depuis un fichier du disque ou depuis un fichier placé sur un serveur HTTP.
Le principe consiste à créer à la place du programmeur l'instance d'objet la plus adaptée à ses besoins. Deux objets seront utilisés : FileDataReader et URLDataReader. L'un et l'autre héritent de la classe DataReader comme le montre le diagramme ci-dessous.
Concevons la classe parente :
public
class
DataReader
{
protected
Reader in =
null
;
public
String read
(
int
bytes) throws
IOException
{
if
(
in ==
null
||
bytes <=
0
)
throw
new
IOException
(
"Cannot read from stream"
);
char
[] c =
new
char
[bytes];
int
read =
in.read
(
c);
return
(
read ==
-
1
? null
: new
String
(
c, 0
, read));
}
}
Notre classe DataReader possède un champ protégé « in » et une méthode publique. La méthode read() généralise l'utilisation du flux de lecture « in ». Nos implémentations FileDataReader et URLDataReader se contenteront de créer une instance spécifique de « in ». La première doit offrir un flux de lecture de fichier :
public
FileDataReader
(
String path)
{
try
{
in =
new
BufferedReader
(
new
FileReader
(
path));
}
catch
(
Exception e) {
}
}
La seconde implémentation doit par contre se préoccuper d'ouvrir un flux de communication sur une URL :
public
URLDataReader
(
String url)
{
try
{
URL netURL =
new
URL
(
url);
in =
new
BufferedReader
(
new
InputStreamReader
(
netURL.openStream
(
)));
}
catch
(
Exception e) {
}
}
Dans un cas comme dans l'autre notre seule préoccupation est de fournir une version adéquate de l'objet « in ». Voyons maintenant comment implémenter une fabrique apte à gérer ces implémentations diverses.
II-B. Introduction d'une fabrique▲
Une fabrique prend toujours pour nom le nom de la super classe du motif auquel on ajoute le suffixe « Factory ». Vous l'avez deviné, notre classe de fabrique se nommera « DataReaderFactory ». Plusieurs méthodes existent pour générer une fabrique :
- la fabrique n'offre que des méthodes statiques qui renvoient les instances souhaitées ;
- la fabrique doit être instanciée par l'opérateur new ;
- la fabrique doit être instanciée par une méthode statique.
Voici des exemples d'utilisation de ces fabriques :
DataReaderFactory.getReader
(
s);
new
DataReaderFactory
(
).getReader
(
s);
DataReaderFactory.createInstance
(
).getReader
(
s);
Notre choix se portera sur la première version étant donné la simplicité de notre structure. Toutes les décisions dans le choix des instances à créer s'effectuent donc au sein de la fabrique.
public
static
DataReader getReader
(
String path)
{
DataReader reader;
if
(
path.startsWith
(
"http://"
))
reader =
new
URLDataReader
(
path);
else
reader =
new
FileDataReader
(
path);
return
reader;
}
Les critères de choix sont ici simplifiés à l'extrême. Le grand intérêt d'un tel motif de conception réside dans la simplicité d'utilisation des implémentations de nos classes. Le fichier Main.java dans le fichier zip contient une petite application d'exemple capable de lire 512 octets dans un flux. L'utilisateur lance le programme en lui donnant en paramètre un nom de fichier ou une URL. Et nous nous contentons de transmettre ce paramètre à la fabrique sans nous soucier de sa nature.
DataReader r =
DataReaderFactory.getReader
(
args[0
]);
System.out.println
(
"Read: "
+
r.read
(
512
));
Vous pourrez exécuter « java Main ~/.bashrc » aussi bien que « java Main http://www.google.fr ».
III. Code source▲
IV. Remerciements▲
Merci à _MAC_ pour sa relecture orthographique.