Personnellement, je n'ai jamais compris l'idée de classes d'usine, car il semble beaucoup plus utile d'instancier directement un objet. Ma question est simple, dans quelle situation l'utilisation d'un modèle de classe d'usine est-elle la meilleure option, pour quelle raison et à quoi ressemble une bonne classe d'usine?
L'idée ici est la séparation des préoccupations: si le code qui utilise l'objet a également suffisamment d'informations pour l'instancier, vous n'avez pas besoin d'une fabrique. Cependant, s'il y a une logique ou une configuration impliquée à laquelle vous ne voulez pas que l'utilisateur de l'API réfléchisse (ou joue avec), vous pouvez masquer tout cela (et l'encapsuler pour une réutilisation) dans une usine.
Voici un exemple: vous souhaitez accéder à l'un des services fournis par Google App Engine. Le même code devrait fonctionner à la fois dans l'environnement de développement (dont il existe deux versions, maître-esclave et haute disponibilité) et dans l'environnement de développement local complètement différent. Google ne veut pas vous parler du fonctionnement interne de son infrastructure interne, et vous ne voulez pas vraiment le savoir. Donc, ce qu'ils font, c'est fournir des interfaces et des usines (et plusieurs implémentations de ces interfaces pour les usines à choisir dont vous n'avez même pas besoin de connaître).
Voici une véritable usine en direct à partir de ma base de code. Il est utilisé pour générer une classe d'échantillonneur qui sait comment échantillonner les données d'un ensemble de données (c'est à l'origine en C #, donc excusez tout Java faux-pas)
class SamplerFactory
{
private static Hashtable<SamplingType, ISampler> samplers;
static
{
samplers = new Hashtable<SamplingType, ISampler>();
samplers.put(SamplingType.Scalar, new ScalarSampler());
samplers.put(SamplingType.Vector, new VectorSampler());
samplers.put(SamplingType.Array, new ArraySampler());
}
public static ISampler GetSampler(SamplingType samplingType)
{
if (!samplers.containsKey(samplingType))
throw new IllegalArgumentException("Invalid sampling type or sampler not initialized");
return samplers.get(samplingType);
}
}
et voici un exemple d'utilisation:
ISampler sampler = SamplerFactory.GetSampler(SamplingType.Array);
dataSet = sampler.Sample(dataSet);
Comme vous le voyez, ce n'est pas beaucoup de code, et il pourrait même être plus court et plus rapide juste pour faire
ArraySampler sampler = new ArraySampler();
dataSet = sampler.Sample(dataSet);
que d'utiliser l'usine. Alors pourquoi est-ce que je dérange même? Eh bien, il y a deux raisons fondamentales, qui s'appuient l'une sur l'autre:
Tout d'abord, c'est la simplicité et la maintenabilité du code. Disons que dans le code appelant, le enum
est fourni comme paramètre. C'est à dire. si j'avais une méthode qui a besoin de traiter les données, y compris l'échantillonnage, je peux écrire:
void ProcessData(Object dataSet, SamplingType sampling)
{
//do something with data
ISampler sampler = SamplerFactory.GetSampler(sampling);
dataSet= sampler.Sample(dataSet);
//do something other with data
}
au lieu d'une construction plus lourde, comme celle-ci:
void ProcessData(Object dataSet, SamplingType sampling)
{
//do something with data
ISampler sampler;
switch (sampling) {
case SamplingType.Scalar:
sampler= new ScalarSampler();
break;
case SamplingType.Vector:
sampler= new VectorSampler();
break;
case SamplingType.Array:
sampler= new ArraySampler();
break;
default:
throw new IllegalArgumentException("Invalid sampling type");
}
dataSet= sampler.Sample(dataSet);
//do something other with data
}
Notez que cette monstruosité doit être écrite chaque fois que j'ai besoin de moi. Et vous pouvez imaginer à quel point il sera amusant de changer si, disons, j'ai ajouté un paramètre au constructeur ScalarSampler
, ou ajouté un nouveau SamplingType
. Et cette usine n'a plus que trois options, imaginez une usine avec 20 implémentations.
Deuxièmement, c'est le découplage du code. Lorsque j'utilise une usine, le code appelant ne sait pas ou n'a pas besoin de savoir qu'une classe appelée ArraySampler
existe même. La classe pourrait même être résolue au moment de l'exécution, et le site d'appel ne serait pas plus sage. Donc, par conséquent, je suis libre de changer la classe ArraySampler
autant que je veux, y compris, mais sans s'y limiter, la suppression pure et simple de la classe, si, par exemple Je décide que le ScalarSampler
doit également être utilisé pour les données du tableau. Je voudrais juste changer de ligne
samplers.put(SamplingType.Array, new ArraySampler());
à
samplers.put(SamplingType.Array, new ScalarSampler());
et cela fonctionnerait comme par magie. Je n'ai pas besoin de changer une seule ligne de code dans les classes appelantes, qui pourrait compter des centaines. En effet, l'usine me permet de contrôler quoi et comment l'échantillonnage se produit, et tout changement d'échantillonnage est efficacement encapsulé dans une seule classe d'usine qui est interfacée avec le reste du système.
Personnellement, j'utilise le modèle d'usine lorsque l'implémentation d'une interface est inconnue au moment de l'exécution ou qu'elle peut être rendue dynamique.
Cela signifie qu'en tant que développeur, je travaille sur une interface connue avec l'instance de l'objet, mais je ne m'inquiète pas du fonctionnement de l'implémentation.
Prends pour exemple. Vous pouvez utiliser un modèle d'usine pour vous fournir des objets d'une base de données. Peu importe que cette base de données soit un fichier plat, une base de données locale/mono-utilisateur, une base de données de serveur ou une ressource Web, seule la fabrique peut générer et gérer ces objets.
Je détesterais devoir écrire des implémentations pour chacun de ces cas: P
Extrait de Effective Java livre de Joshua Bloch, partiellement réécrit par moi:
1) Les méthodes d'usine statiques ( SFM ), contrairement aux constructeurs, ont des noms.
public static ComplexNumber one () {
return new ComplexNumber(1, 0);
}
public static ComplexNumber imgOne () {
return new ComplexNumber(0, 1);
}
public static ComplexNumber zero () {
return new ComplexNumber(0, 0);
}
2) Il n'est pas nécessaire de créer un nouvel objet à chaque fois que SFM
est/sont invoqués
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
3) SFM
peut renvoyer un objet de n'importe quel sous-type de leur type de retour.
4) SFM
réduit la verbosité de la création d'instances de type paramétré.
public static <K, V> HashMap<K, V> newInstance() {
return new HashMap<K, V>();
}
Map<String, List<String>> m = HashMap.newInstance();
Pour compléter la réponse de Thilo, supposons que vous ayez un objet qui n'a qu'un booléen comme constructeur: ce serait un gaspillage total d'en construire un à chaque fois, puisqu'il n'a que deux valeurs possibles.
Dans ce cas, vous pouvez créer des méthodes d'usine statiques. La classe Boolean
de Java est un exemple: Boolean.valueOf()
.
L'usine en elle-même ne montre pas sa beauté si facilement. C'est lorsque vous le combinez avec d'autres modèles lorsque vous voyez les avantages réels, par exemple, si vous souhaitez utiliser le modèle de décoration, l'instanciation directe d'un objet peut ajouter un couplage supplémentaire à votre code. Comme le dit l'enseignant OOP, le couplage est mauvais :) donc si vous instanciez l'objet décoré et ne voulez pas augmenter le couplage, vous pouvez utiliser une usine.
Vous pouvez vous référer à wikipedia , mais l'idée de base de la plupart des modèles de conception est d'introduire une certaine abstraction pour obtenir une meilleure maintenabilité et/ou réutilisation. Le modèle de méthode d'usine ne fait pas exception, ce qu'il fait est d'abstraire la complexité de la création du code.
Pour un cas simple, il semble inutile d'utiliser un modèle d'usine, un simple new
suffit. Mais lorsque vous avez besoin de plus de flexibilité ou de fonctionnalités, ce modèle peut vous aider.
Par exemple, à moins qu'une nouvelle instance ne soit requise, la fabrique statique valueOf(boolean)
est généralement un meilleur choix que new Bealean(boolean)
, car elle évite de créer des objets inutiles. Le modèle de méthode d'usine est également appelé Constructeur virtuel . Comme nous le savons, le polymorphisme est l'une des principales caractéristiques de la POO, mais le constructeur ne peut pas être polymorphe, cette lacune peut être surmontée par un modèle de méthode d'usine.
Essentiellement, l'instanciation directe d'un objet (généralement via new
) n'est guère concrète implémentation, tandis que le modèle de méthode d'usine protège a volatile implémentation par une interface stable (pas limité à interface
en Java), poussant la logique de la création d'objets derrière une certaine abstraction pour assurer un code plus maintenable et réutilisable.
Comme mot final, pour bien comprendre les avantages du modèle de méthode d'usine et d'autres modèles de conception, il faut comprendre l'essence de la POO, y compris abstraction des données , abstraction polymorphe et SOLIDE principe.