POO & Design Patterns

Les design patterns factory

Les patrons Factory

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.

  • static factory method (v0 et v1)
  • factory (class)
  • abstract factory (GoF)
  • factory method (GoF, utilisé surtout dans le code vintage)

Static factory method (v0)

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:

  • tous les constructeurs ont le même nom
  • deux constructeurs différents ne peuvent pas avoir la même signature
  • les constructeurs doivent juste faire des affectations et des vérifications
  • en cas de problème dans un constructeur, la seule solution est de lever une exception

Exemple Line (1/3)

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.

Exemple Line (2/3)

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);
	}
}

Exemple Line (3/3)

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.

System.console()

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().

Static factory method (v1)

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 !

Exemple 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": { ...
}

Autres exemples dans l'API

  • Path Paths.get(String pathname) renvoie un objet qui implémente l'interface Path.
    Le type concret de l'objet retourné dépend du système de fichier.
  • InetAddress InetAddress.byName(String address) renvoie une sous-classe de InetAddress selon que l'adresse crée est une adresse IPv4 ou IPv6.

Static factory method (v1)

Le client n'a pas à connaitre les implémentations concrètes mais simplement l'interface.

Des dangers de ne pas avoir d'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.

Factory (class)

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, ...).

StudentFactory

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.

Abstract factory

Une Abstract Factory est une interface commmune pour plusieurs (classes) Factory.

Il y a donc:

  • Une interface commune aux objets construits
  • Une interface commune pour les factories

C'est exactement ce que nous avons fait avec CanvasFactory et Canvas.

Abstract Factory : CanvasFactory (2/4)

Abstract Factory (3/4)

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.

Abstract Factory (4/4)

Factory Method

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!

Exemple

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);
	}

}

Avec la délégation ...

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.