Même si l'on peut déclarer des variables d'un type abstrait, les constructeurs font nécessairement partie d'un type concret. Une application qui contient un grand nombre d'appels à des constructeurs est donc très dépendante des classes concrètes choisies. Si l'on décide de changer la réalisation de certains types abstraits, il faut remplacer toutes les invocations des constructeurs des classes concrètes correspondantes, ce qui n'est pas commode et sujet à erreurs. Il est préférable de rassembler tous les choix de réalisation dans un objet particulier, appelé fabrique, auquel sera déléguée la construction des objets. Cette technique constitue le pattern de fabrication.
Prenons l'exemple d'une application qui doit utiliser des listes, des ensembles et des tables. Les variables et les paramètres des méthodes seront déclarées à l'aide des interfaces List, Set et Map.
La construction de listes, d'ensembles et de tables est déléguée à une fabrique f, déclarée d'un type abstrait Fabrique. La fabrique est créée à l'aide d'un constructeur d'une classe concrète qui rassemble les choix de réalisations. Ici la classe MaFabrique indique que les réalisations ArrayList, HashSet et HashMap sont choisies. Dans la suite du programme, là où l'on aurait invoqué un constructeur, new ArrayList(), new HashSet() ou new HashMap(), on invoque une méthode déclarée dans le type abstrait Fabrique sur l'objet fabrique : respectivement f.fabriquerList(), f.fabriquerSet() ou f.fabriquerMap() :
Fabrique f = new MaFabrique(); List l = f.fabriquerList(); Set s = f.fabriquerSet(); Map m = f.fabriquerMap();
Fabrique est une interface, et c'est en la réalisant qu'on décide quelles réalisations de List vont être utilisées :
interface Fabrique { List fabriquerList(); Set fabriquerSet(); Map fabriquerMap(); } class MaFabrique implements Fabrique { List fabriquerList(){ return new ArrayList(); } Set fabriquerSet(){ return new HashSet(); } Map fabriquerMap(){ return new HashMap(); } }
Si d'autres choix sont à faire, il suffit de modifier la classe concrète MaFabrique ; mieux, on définit une autre classe concrète réalisant Fabrique et on modifie la seule ligne Fabrique f = new MaFabrique(). Si l'on préfère, pour des raisons de préservation de l'ordre des données, des réalisations par des ensembles et des tables ordonnés, on définira :
class FabriqueParArbres implements Fabrique { List fabriquerList(){ return new ArrayList(); } Set fabriquerSet(){ return new TreeSet(); } Map fabriquerMap(){ return new TreeMap(); } }
Il suffira de définir une fabrique par :
Fabrique f = new FabriqueParArbres();
Le reste du programme n'a pas besoin d'être modifié. Dans une approche plus systématique, l'application figurerait dans une classe Application, dont un constructeur aurait un paramètre de type Fabrique. La création de l'application se ferait par :
... new Application(new MaFabrique()) ...