Design Pattern – TD n°2: Factory et Factory Builder
Le Pattern Factory
Le but est d'implémenter le modèle Factory avec un exemple simple pour en comprendre le fonctionnement.
Dans ce projet, deux classes existent : une classe Program1 affichant un message lors de l'appel à go, et une classe Client qui utilise Program1.
public class Client {
public static void main1() {
Program1 p = new Program1();
System.out.println("Je suis le main1");
p.go();
}
public static void main2() {
Program1 p = new Program1();
System.out.println("Je suis le main2");
p.go();
}
public static void main3() {
Program1 p = new Program1();
System.out.println("Je suis le main3");
p.go();
}
}
public class Program1 {
public Program1() { // Le constructeur ne fait rien }
public void go() {
System.out.println("Je suis le traitement 1");
}
}
1. Ajoutez deux classes Program2 et Program3 affichant le même message avec leur numéro respectif (2 ou 3). Voici leur implémentation :
public class Program2 {
public void go() {
System.out.println("Je suis le traitement 2");
}
}
public class Program3 {
public void go() {
System.out.println("Je suis le traitement 3");
}
}
2. Modifiez le code des fonctions main dans la classe Client pour qu'elles lancent le traitement correspondant au paramètre entier passé en argument (1, 2 ou 3).
public class Client {
public static void main(int type) {
switch (type) {
case 1:
Program1 p1 = new Program1();
p1.go();
break;
case 2:
Program2 p2 = new Program2();
p2.go();
break;
case 3:
Program3 p3 = new Program3();
p3.go();
break;
default:
System.out.println("Type inconnu");
}
}
}
3. Créez une fonction commune pour éviter la duplication de code dans les trois méthodes main. Ajoutez une méthode createProgram dans la classe Client :
public class Client {
public static void main(int type) {
ProgramInterface p = createProgram(type);
p.go();
}
public static ProgramInterface createProgram(int type) {
switch (type) {
case 1:
return new Program1();
case 2:
return new Program2();
case 3:
return new Program3();
default:
return null;
}
}
}
public interface ProgramInterface {
public void go();
}
4. Implémentez l'interface ProgramInterface pour les classes Program1, Program2 et Program3.
5. Ajoutez une classe Program4 et vérifiez que l'ajout est simple sans modifier le code du Client.
Le Pattern Abstract Factory
Ce pattern étend le concept de Factory pour créer des groupes de produits sans modifier le code existant.
1. Créez une interface FileNameParser avec deux implémentations : ParseFileNameWindows et ParseFileNameLinux.
public interface FileNameParser {
public String parse(String path);
}
public class ParseFileNameWindows implements FileNameParser {
public String parse(String path) {
int index = path.lastIndexOf("\\");
return path.substring(index + 1);
}
}
public class ParseFileNameLinux implements FileNameParser {
public String parse(String path) {
int index = path.lastIndexOf("/");
return path.substring(index + 1);
}
}
2. Ajoutez une interface CountFolders avec deux implémentations pour compter les dossiers : CountFoldersWindows et CountFoldersLinux.
public interface CountFolders {
public int count(String path);
}
public class CountFoldersWindows implements CountFolders {
public int count(String path) {
int count = 0;
int index = path.lastIndexOf("\\");
while (index != -1) {
count++;
index = path.substring(0, index).lastIndexOf("\\");
}
return count;
}
}
public class CountFoldersLinux implements CountFolders {
public int count(String path) {
int count = 0;
int index = path.lastIndexOf("/");
while (index != -1) {
count++;
index = path.substring(0, index).lastIndexOf("/");
}
return count;
}
}
3. Créez une Factory abstraite avec deux implémentations : WindowsFactory et LinuxFactory.
public interface Factory {
public FileNameParser createFileNameParser();
public CountFolders createCountFolders();
}
public class WindowsFactory implements Factory {
public FileNameParser createFileNameParser() {
return new ParseFileNameWindows();
}
public CountFolders createCountFolders() {
return new CountFoldersWindows();
}
}
public class LinuxFactory implements Factory {
public FileNameParser createFileNameParser() {
return new ParseFileNameLinux();
}
public CountFolders createCountFolders() {
return new CountFoldersLinux();
}
}
4. Ajoutez une FactoryFactory pour éviter de choisir explicitement la factory dans la classe Main.
public class FactoryFactory {
public static Factory getFactory(String os) {
if (os.equalsIgnoreCase("Windows")) {
return new WindowsFactory();
} else if (os.equalsIgnoreCase("Linux")) {
return new LinuxFactory();
} else if (os.equalsIgnoreCase("Mac")) {
return new MacFactory();
}
return null;
}
}
5. Ajoutez le support pour les chemins Macintosh avec une classe ParseFileNameMac et CountFoldersMac.
public class ParseFileNameMac implements FileNameParser {
public String parse(String path) {
int index = path.lastIndexOf(":");
if (index == -1) {
index = path.lastIndexOf("/");
}
return path.substring(index + 1);
}
}
public class CountFoldersMac implements CountFolders {
public int count(String path) {
int count = 0;
int index = path.lastIndexOf(":");
if (index == -1) {
index = path.lastIndexOf("/");
}
while (index != -1) {
count++;
index = path.substring(0, index).lastIndexOf(":");
if (index == -1) {
index = path.substring(0, index).lastIndexOf("/");
}
}
return count;
}
}
public class MacFactory implements Factory {
public FileNameParser createFileNameParser() {
return new ParseFileNameMac();
}
public CountFolders createCountFolders() {
return new CountFoldersMac();
}
}
6. Ajoutez une fonctionnalité pour obtenir le nom du répertoire source contenant le fichier. Modifiez les interfaces et les implémentations en conséquence.
Factory et le Vidéo Club
Utilisez le Pattern Factory pour gérer la création des éléments louables dans un Vidéo Club.
1. Créez une interface ElementLocation avec les champs suivants : type, prix, titre, nombre de copies totales, un enum pour le type d'élément, nombre de copies disponibles, liste des identités des copies, et liste des identités des copies disponibles.
public enum ElementType {
FILM_DVD,
FILM_BLURAY,
JEU_VIDEO
}
public interface ElementLocation {
public String getType();
public float getPrix();
public String getTitre();
public int getNombreCopiesTotales();
public ElementType getElementType();
public int getNombreCopiesDisponibles();
public List getIdentitesCopies();
public List getIdentitesCopiesDisponibles();
}
2. Implémentez la classe FilmsDVD avec des informations supplémentaires : durée, langues disponibles et résumé.
public class FilmsDVD implements ElementLocation {
private String type = "DVD";
private float prix;
private String titre;
private int nombreCopiesTotales;
private ElementType elementType = ElementType.FILM_DVD;
private int nombreCopiesDisponibles;
private List identitesCopies;
private List identitesCopiesDisponibles;
private int duree;
private List languesDisponibles;
private String resume;
public FilmsDVD(float prix, String titre, int nombreCopiesTotales, int duree, List languesDisponibles, String resume) {
this.prix = prix;
this.titre = titre;
this.nombreCopiesTotales = nombreCopiesTotales;
this.duree = duree;
this.languesDisponibles = languesDisponibles;
this.resume = resume;
this.nombreCopiesDisponibles = nombreCopiesTotales;
this.identitesCopies = new ArrayList<>();
this.identitesCopiesDisponibles = new ArrayList<>();
for (int i = 0; i < nombreCopiesTotales; i++) {
this.identitesCopies.add("DVD-" + (i + 1));
this.identitesCopiesDisponibles.add("DVD-" + (i + 1));
}
}
// Implémentez les méthodes getters
}
3. Ajoutez les classes FilmBluray et JeuxVideo avec leurs champs spécifiques.
public class FilmBluray implements ElementLocation {
private String type = "BLURAY";
private float prix;
private String titre;
private int nombreCopiesTotales;
private ElementType elementType = ElementType.FILM_BLURAY;
private int nombreCopiesDisponibles;
private List identitesCopies;
private List identitesCopiesDisponibles;
private int duree;
private List languesDisponibles;
private String resume;
private boolean estEn3D;
public FilmBluray(float prix, String titre, int nombreCopiesTotales, int duree, List languesDisponibles, String resume, boolean estEn3D) {
this.prix = prix;
this.titre = titre;
this.nombreCopiesTotales = nombreCopiesTotales;
this.duree = duree;
this.languesDisponibles = languesDisponibles;
this.resume = resume;
this.estEn3D = estEn3D;
this.nombreCopiesDisponibles = nombreCopiesTotales;
this.identitesCopies = new ArrayList<>();
this.identitesCopiesDisponibles = new ArrayList<>();
for (int i = 0; i < nombreCopiesTotales; i++) {
this.identitesCopies.add("BLURAY-" + (i + 1));
this.identitesCopiesDisponibles.add("BLURAY-" + (i + 1));
}
}
// Implémentez les méthodes getters
}
public class JeuxVideo implements ElementLocation {
private String type = "JEU_VIDEO";
private float prix;
private String titre;
private int nombreCopiesTotales;
private ElementType elementType = ElementType.JEU_VIDEO;
private int nombreCopiesDisponibles;
private List identitesCopies;
private List identitesCopiesDisponibles;
private int nombreJoueurs;
private Console console;
public JeuxVideo(float prix, String titre, int nombreCopiesTotales, int nombreJoueurs, Console console) {
this.prix = prix;
this.titre = titre;
this.nombreCopiesTotales = nombreCopiesTotales;
this.nombreJoueurs = nombreJoueurs;
this.console = console;
this.nombreCopiesDisponibles = nombreCopiesTotales;
this.identitesCopies = new ArrayList<>();
this.identitesCopiesDisponibles = new ArrayList<>();
for (int i = 0; i < nombreCopiesTotales; i++) {
this.identitesCopies.add("JEU-" + (i + 1));
this.identitesCopiesDisponibles.add("JEU-" + (i + 1));
}
}
// Implémentez les méthodes getters
}
4. Créez une interface Console et Language avec leurs implémentations respectives.
public enum Console {
PS4,
XBOX,
NINTENDO_SWITCH,
PC
}
public enum Language {
FRANCAIS,
ANGLAIS,
ALLEMAND,
ESPAGNOL
}
5. Développez la factory pour gérer la création des éléments du Vidéo Club.
public class VideoClubFactory {
public ElementLocation createElement(String type, float prix, String titre, int nombreCopiesTotales) {
switch (type) {
case "DVD":
return new FilmsDVD(prix, titre, nombreCopiesTotales, 120, Arrays.asList(Language.FRANCAIS, Language.ANGLAIS), "Résumé");
case "BLURAY":
return new FilmBluray(prix, titre, nombreCopiesTotales, 120, Arrays.asList(Language.FRANCAIS, Language.ANGLAIS), "Résumé", true);
case "JEU_VIDEO":
return new JeuxVideo(prix, titre, nombreCopiesTotales, 4, Console.PS4);
default:
return null;
}
}
}
FAQ
Qu'est-ce qu'un Pattern Factory ?
Un Pattern Factory est un design pattern qui permet de déléguer la création d'objets à une classe spécifique, évitant ainsi la duplication de code et simplifiant l'ajout de nouveaux types d'objets.
Pourquoi utiliser une Factory plutôt qu'une création directe ?
Utiliser une Factory permet de centraliser la logique de création des objets, facilitant ainsi les modifications futures et évitant que les classes clientes ne dépendent des implémentations concrètes.
Quelle est la différence entre Factory et Abstract Factory ?
Le Pattern Factory crée un seul type de produit, tandis que le Pattern Abstract Factory crée des familles de produits liés, en utilisant plusieurs factories spécifiques pour chaque type de famille.