Activité : Patrons de conception pour la création d’objet

Factory et FactoryMethod

Ce travail a pour but d’introduire des patrons de conception permettant d’instancier des objets.

Objectifs

À la fin de ce travail, vous devez :

  1. Être capable d’expliquer le patron de conception Abstract Factory.
  2. Être capable d’expliquer le patron de conception Factory Method.

Résultat attendu

  • Un petit document où vous aurez décrit ce que vous avez fait et les difficultés que vous aurez rencontrées.

Ressources

Logiciel :

  • NetBeans

Mise en route

Création d’une classe Person

Pour commencer, téléchargez l’archive du projet ContactManager.zip qui se trouve sur la plateforme moodle et importez-le dans NetBeans. NetBeans vous indique que le projet contient des erreurs et vous ne pouvez donc ni exécuter les tests ni lancer l’application.

Pour corriger cela, implémentez une classe Person selon le diagramme ci-dessous.

Séparation de l’interface et de l’implémentation

Fig. 1 – Séparation de l’interface (type) et de l’implémentation de HttpServer.

Ouvrez maintenant le menu contextuel sur le projet et cliquez sur Test pour lancer les tests unitaires. Si les tests de la classe Person (PersonTest) ne passent pas, corrigez la classe en conséquence. Vous ne devez pas poursuivre tant que PersonTest n’est pas vert.

Créer une liste de personne à partir d’un fichier CSV

Dans le répertoire de votre projet, vous trouvez un fichier CSV qui contient une liste de personnes. Il s’agit maintenant de créer une classe qui permet de charger ce fichier dans un objet ArrayList.

La classe PersonDataMapper est a déjà été préparée pour vous, mais c’est à vous de l’implémenter. Dans le fichier CSV, chaque ligne représente une personne et les champs de données sont séparés par des points-virgules. Chaque enregistrement contient les champs suivants :

  • id
  • firstname
  • lastname
  • street
  • postalCode
  • city
  • email

Dans la figure 2, vous trouvez un extrait de code qui devraient vous aider à réaliser ce travail.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    private void readFile(String fileName) throws IOException {
        // Lit toutes les lignes d’un fichier de texte et les stocke dans une 
        // liste de chaîne.
        List<String> lines = Files.readAllLines(Paths.get(fileName), StandardCharsets.UTF_8);
        
        // Itère la liste liste de chaîne (pour chaque chaîne de la liste)
        for (String line : lines) {
            // Scinde la ligne en un tableau. Le paramètre de split est le caractère
            // utilisé pour séparer les champs (ici le caractère de tabulation).
            String[] fields = line.split("\t")

            // TODO : faire quelque chose avec les champs (par exemple créer un objet).

        }
    }
Fig. 2 – Lecture d’un fichier de type CSV

Créer une liste de personne à partir d’un fichier XML

Dans le répertoire de votre projet, vous trouvez également un fichier XML qui contient la même liste de personnes. De la même manière, il s’agit de charger cette liste dans un objet de type ArrayList

Pour charger le contenu d’un fichier XML sous forme d’objets, nous allons utiliser la bibliothèque libre XStream. La figure 3 montre comment utiliser cette bibliothèque pour charger une liste de personnes telle que celle qui se trouve dans le fichier.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    private void readFile(String fileName) throws IOException {
        // Crée une instance de XStream.
        XStream xs = new XStream(new PureJavaReflectionProvider(), new DomDriver());
        
        // XStream utilise l’introspection de Java (Java reflection) pour 
        // connaître la structure de la classe Person et initialiser les
        // variable membre. Au cas où certains éléments XML seraient vides
        // ou absents, il est important que les variables aient été initialisées
        // dans le constructeur par défaut (le constructeur sans paramètre).

        // Obtient un objet qui représente la classe Person.
        Class personClass = Person.class;
        
        // Définit le mapping entre le nom des éléments XML et la classe 
        // auxquels ils correspondent.
        xs.alias("person-list", ArrayList.class);
        xs.alias("person", personClass);

        // Définit le mapping entre le nom des sous-éléments de l’élément
        // XML "person" (à gauche) et le nom des variables membres de 
        // la classe "Person" (à droite).
        xs.aliasField("id", personClass, "id");
        xs.aliasField("firstname", personClass, "firstname");
        xs.aliasField("lastname", personClass, "lastname");
        xs.aliasField("address", personClass, "street");
        xs.aliasField("postal-code", personClass, "postalCode");        
        xs.aliasField("city", personClass, "city");
        xs.aliasField("email", personClass, "emailAddress");
        
        // Lit le fichier et crée les objets.
        FileInputStream input = new FileInputStream(fileName);
        this.persons = (List<Person>)xs.fromXML(input);
    }
Fig. 3 – Lecture d’un fichier de type XML

Utilisez les tests pour vous assurer que votre implémentation fonctionne selon la spécification. Vous ne pouvez pas poursuivre votre travail tant que le test de la class PersonDataMapperXml (PersonDataMapperXmlTest) n’est vert.

Créer un mapper en fonction du type de fichier

Les deux masers que nous avons réalisés sont deux sous-types du type PersonDataMapper. Le client de ces objets n’a donc pas à savoir si les données proviennent d’un fichier CSV ou d’un fichier XML.

Cependant, dans l’état actuel des choses, pour utiliser l’un de ces mappers, on doit créer une instance de PersonDataMapperCsv ou de PersonDataMapperXml. C’est donc au client de savoir lequel instancier en fonction du type du fichier. Pour simplifier l’utilisation, nous allons utiliser le patron de conception Abstract Factory.

Il s’agit de définir un type qui offre une ou plusieurs opérations permettant de créer un objet. Dans notre cas, le type que nous allons définir ne contient qu’une seule opération qui doit créer un objet de type PersonDataMapper à partir du nom de fichier qu’on lui passe en paramètre. La figure 4 montre le diagramme UML du système à implémenter.

Séparation de l’interface et de l’implémentation

Fig. 4 – Séparation de l’interface (type) et de l’implémentation de HttpServer.

En Java, la classe String dispose d’une méthode endWith qui peut sans doute vous être utile pour trouver l’extention de fichier. Utilisez là encore les tests unitaire pour valider votre code.

Utiliser le système

Vous pouvez maintenant écrire un petit programme pour mettre en oeuvre le système que vous avez réalisé. La figure 5 vous présente un exemple.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.List;

public class ContactManager {

    public static void main(String[] args) {

        DataMapperFactory factory = new DataMapperFactory();
        
        String fileName = "person.xml"; // ou "person.csv" les deux fonctionnent

        PersonDataMapper mapper = factory.createDataMapper(fileName); 
        List<Person> persons = mapper.findAll();
        
        for (Person p : persons) {
            System.out.println(p);
        }
    }
}
Fig. 5 – Exemple d'utilisation