Il existe plusieurs patrons basés sur le principe d'une usine (factory).
Malheureusement, il n'y a pas de consensus sur les noms précis de ces patrons.
En particulier, les patrons factory du GoF sont peu utilisés en pratique.
⇛ Attention aux mal-entendus.
C'est une méthode static qui s'occupe de la création des objets d'une classe concrète.
Propose des solutions aux limitations des constructeurs:
public class Line{ private int x1; private int y1; private int x2; private int y2; public Line(int x1,int y1,int x2,int y2){ this.x1=x1; this.y1=y1; this.x2=x2; this.y2=y2; } }
On voudrait pouvoir créer une ligne à partir d'un point et d'un vecteur.
public class Line{ int x1; int y1; int x2; int y2; public Line(int x1,int y1,int x2,int y2){ ... } public static Line fromPointAndVector(int x,int y, int vx, int vy){ return new Line(x,y,x+vx,y+vy); } }
public class Line{ int x1; int y1; int x2; int y2; private Line(int x1,int y1,int x2,int y2){ ... } public static Line fromExtremities(int x1,int y1, int y1, int y2){ return new Line(x1,y1,x2,y2); } public static Line fromPointAndVector(int x,int y, int vx, int vy){ return new Line(x,y,x+vx,y+vy); } }
On peut rendre le(s) constructeur(s) privé(s) et autoriser la création d'objets exclusivement par les factories.
La méthode static Console System.console()
renvoie un objet java.io.Console
s'il en existe une et null
sinon.
⇛ Les factories permettent de gérer le cas où la création de l'objet n'est pas possible sans avoir recours aux exceptions.
Une meilleure signature pour Console System.console()
serait Optional<Console> System.console()
.
C'est une méthode static qui s'occupe de la création d'objets d'un même super-type.
Par rapport à la (v0), la méthode factory ne renvoie plus un type concret mais un type abstrait (comme une interface).
⇛ Dependency Inversion Principle !
createShape
Le type de l'objet construit peut dépendre des arguments de la construction.
public static Shape createShape(String[] tokens) { switch (tokens[0]) { case "line": { int x0 = Integer.parseInt(tokens[1]); int y0 = Integer.parseInt(tokens[2]); int x1 = Integer.parseInt(tokens[3]); int y1 = Integer.parseInt(tokens[4]); return new Line(x0, y0, x1, y1); } case "ellipse": { ... }
Path Paths.get(String pathname)
renvoie un objet qui implémente l'interface Path
.
InetAddress InetAddress.byName(String address)
renvoie une sous-classe de InetAddress
selon que l'adresse crée est une adresse IPv4 ou IPv6.Le client n'a pas à connaitre les implémentations concrètes mais simplement l'interface.
InetAddress
n'est pas une interface:
historiquement il n'y avait qu'un seul type d'adresse IP.
Du coup, Inet4Address
et Inet6Address
héritent de InetAddress
.
⇛ Clairement, il aurait fallu faire une interface.
Une (classe) Factory est une classe ayant pour responsabilité la création d'objets d'un certain type.
Une façon plus moderne de le dire, c'est simplement Supplier<T>
Par example, CoolGraphicsAdapterFactory
est une classe factory pour le type Canvas
.
La différence est que la factory peut avoir un état (pooling, ...).
public class UGEStudentFactory{ private long currentId; private HashMap<Long,Student> registeredStudents = new HashMap<>(); public Student createStudent(String firstName,String lastName){ var student = new UGEStudent(currendId,firstName,lastName); registeredStudents.put(currentId,student); currentId++; return student; } public Optional<Student> getStudentById(long id){ return Optional.ofNullable(registeredStudents.get(id)); } }
public class UGEStudent implements Student{ private final long currentId; private final String firstName; private final String lastName; UGEStudent(long id, String firstName, String lastName){ ... } }
Le constructeur peut ne pas être public pour éviter la création d'objets à l'extérieur du package de la factory.
Une Abstract Factory est une interface commmune pour plusieurs (classes) Factory.
Il y a donc:
C'est exactement ce que nous avons fait avec CanvasFactory
et Canvas
.
Le pattern du GoF est un peu plus général car il permet de créer différents types d'objets qui sont liés entre eux.
Dans notre exemple, on ne crée que des Canvas
qui sont des fenêtres.
Imaginons que l'on veuille ajouter des boutons.
On aura une interface Button
qui va être interdépendante avec Canvas
.
L'abstract factory sera une interface permettant de créer les boutons et les fenêtres.
Ce patron est un faux-ami. C'est un des rares patrons du GoF à utiliser l'héritage et il est très peu utilisé en pratique.
Il consiste à définir une méthode abstraite de type factory qui sera redéfinie par les classes héritantes.
On peut obtenir le même comportement par composition et délégation!
abstract class ClassRoomCreator{ ... public abstract Student createStudent(String firstName, String lastName); public ClassRoom fromXML(Path filename){ .... // XML Parsing createStudent(studentElement.firstName,studentElement.lastName); .... } }
public class UGEClassRoomCreator extends ClassRoomCreator{ @Override public Student createStudent(String firstName, String lastName){ return new UGEStudent(firstName,lastName); } }
public class UPECClassRoomCreator extends ClassRoomCreator{ @Override public Student createStudent(String firstName, String lastName){ return new UPECStudent(firstName,lastName); } }
public class ClassRoomCreator{ public ClassRoom fromXML(Path filename, StudentFactory studentFactory){ .... // XML Parsing sutdentFactory.createStudent(studentElement.firstName,studentElement.lastName); .... } }
⇛ L'héritage est remplacé par la délégation.