Constructeurs vs méthodes d'usine [fermé]


181

Lors de la modélisation de classes, quelle est la méthode d'initialisation préférée:

  1. Constructeurs, ou
  2. Méthodes d'usine

Et quelles seraient les considérations pour utiliser l'un ou l'autre?

Dans certaines situations, je préfère avoir une méthode de fabrique qui retourne null si l'objet ne peut pas être construit. Cela rend le code soigné. Je peux simplement vérifier si la valeur retournée n'est pas nulle avant de prendre une action alternative, contrairement à la levée d'une exception du constructeur. (Personnellement, je n'aime pas les exceptions)

Dites, j'ai un constructeur sur une classe qui attend une valeur id. Le constructeur utilise cette valeur pour remplir la classe à partir de la base de données. Dans le cas où un enregistrement avec l'ID spécifié n'existe pas, le constructeur lève une RecordNotFoundException. Dans ce cas, je devrai inclure la construction de toutes ces classes dans un bloc try..catch.

Contrairement à cela, je peux avoir une méthode de fabrique statique sur ces classes qui renverra null si l'enregistrement n'est pas trouvé.

Quelle approche est la meilleure dans ce cas, méthode constructeur ou méthode d'usine?

Réponses:


66

À partir de la page 108 des modèles de conception: éléments de logiciels orientés objet réutilisables de Gamma, Helm, Johnson et Vlissides.

Utilisez le modèle de méthode d'usine lorsque

  • une classe ne peut pas anticiper la classe d'objets qu'elle doit créer
  • une classe souhaite que ses sous-classes spécifient les objets qu'elle crée
  • les classes délèguent la responsabilité à l'une des nombreuses sous-classes d'assistance, et vous souhaitez localiser la connaissance de la sous-classe d'assistance qui est le délégué

21
La méthode d'usine statique est différente du modèle de conception GoF - Modèle de méthode d'usine. stackoverflow.com/questions/929021/…
Sree Rama

Veuillez ne pas comparer avec le modèle de méthode d'usine des modèles de conception GoF.
Sree Rama

137
cela ne m'explique rien
Sushant

@Sushant, pourquoi en est-il ainsi?
PaulD

2
Cette réponse ne répond pas à la question se contente de relayer les informations à lire pour comprendre / expliquer le concept ... C'est plus un commentaire.
Crt

202

Demandez-vous ce qu'ils sont et pourquoi nous les avons. Ils sont tous deux là pour créer une instance d'un objet.

ElementarySchool school = new ElementarySchool();
ElementarySchool school = SchoolFactory.Construct(); // new ElementarySchool() inside

Aucune différence jusqu'à présent. Imaginez maintenant que nous avons différents types d'écoles et que nous souhaitons passer de ElementarySchool à HighSchool (qui est dérivé d'une ElementarySchool ou implémente la même interface ISchool que ElementarySchool). Le changement de code serait:

HighSchool school = new HighSchool();
HighSchool school = SchoolFactory.Construct(); // new HighSchool() inside

Dans le cas d'une interface, nous aurions:

ISchool school = new HighSchool();
ISchool school = SchoolFactory.Construct(); // new HighSchool() inside

Maintenant, si vous avez ce code à plusieurs endroits, vous pouvez voir que l'utilisation de la méthode d'usine peut être assez bon marché car une fois que vous avez changé la méthode d'usine, vous avez terminé (si nous utilisons le deuxième exemple avec des interfaces).

Et c'est la principale différence et avantage. Lorsque vous commencez à traiter des hiérarchies de classes complexes et que vous souhaitez créer dynamiquement une instance d'une classe à partir d'une telle hiérarchie, vous obtenez le code suivant. Les méthodes d'usine peuvent alors prendre un paramètre qui indique à la méthode quelle instance concrète instancier. Disons que vous avez une classe MyStudent et que vous devez instancier l'objet ISchool correspondant afin que votre élève soit membre de cette école.

ISchool school = SchoolFactory.ConstructForStudent(myStudent);

Vous disposez désormais d'un emplacement dans votre application qui contient la logique métier qui détermine l'objet ISchool à instancier pour différents objets IStudent.

Donc - pour les classes simples (objets de valeur, etc.), le constructeur est très bien (vous ne voulez pas surenginer votre application) mais pour les hiérarchies de classes complexes, la méthode factory est un moyen préféré.

De cette façon, vous suivez le premier principe de conception du livre de la bande de quatre "Programme vers une interface, pas une implémentation".


2
Même lorsque vous pensez que c'est une classe simple, il y a une chance que quelqu'un ait besoin d'étendre votre classe simple, donc la méthode d'usine est encore meilleure. Par exemple, vous pouvez commencer avec ElementarySchool mais plus tard quelqu'un (y compris vous-même) peut l'étendre avec PrivateElementarySchool et PublicElementarySchool.
jack

10
cela devrait être la réponse acceptée
am05mhz

2
@David, bonne réponse, mais pouvez-vous développer un exemple où chaque implémentation d'interface peut nécessiter des paramètres différents pour la construction. Voici un exemple idiot: IFood sandwich = new Sandwich(Cheese chz, Meat meat);et IFood soup = new Soup(Broth broth, Vegetable veg);comment l'usine et / ou le constructeur peuvent-ils aider ici?
Brian

1
Je viens de lire trois autres explications sur le but de l'utilisation d'usine, et c'est celle qui a finalement eu le "clic" pour moi. Je vous remercie!
Daniel Peirano

Pourquoi ce n'est pas une réponse acceptée?
Tomas

74

Vous devez lire (si vous avez accès à) Java 2 efficace. Élément 1: Considérez les méthodes de fabrique statiques au lieu des constructeurs .

Avantages des méthodes statiques d'usine:

  1. Ils ont des noms.
  2. Ils ne sont pas obligés de créer un nouvel objet à chaque fois qu'ils sont appelés.
  3. Ils peuvent renvoyer un objet de n'importe quel sous-type de leur type de retour.
  4. Ils réduisent la verbosité de la création d'instances de types paramétrés.

Inconvénients des méthodes d'usine statiques:

  1. Lorsque vous ne fournissez que des méthodes de fabrique statiques, les classes sans constructeurs publics ou protégés ne peuvent pas être sous-classées.
  2. Ils ne se distinguent pas facilement des autres méthodes statiques

4
Cela me semble plutôt un bogue sévère en Java, puis un problème général d'OOD. Il existe de nombreux langages OO qui n'ont même pas de constructeur, mais le sous-classement fonctionne très bien.
Jörg W Mittag

1
@cherouvim pourquoi le code est principalement écrit à l'aide de constructeurs if( factory methods are better than Constructors. ( Item-1 ) ) Effective java
Asif Mushtaq

Bons points. C'est cependant spécifique à Java. Un cas peut être fait pour une fonctionnalité de langage qui distingue les méthodes de fabrique des autres méthodes statiques.
OCDev

30

Par défaut, les constructeurs doivent être préférés, car ils sont plus simples à comprendre et à écrire. Cependant, si vous avez spécifiquement besoin de découpler les subtilités de construction d'un objet de sa signification sémantique telle que comprise par le code client, vous feriez mieux d'utiliser des usines.

La différence entre les constructeurs et les usines est analogue, par exemple, à une variable et à un pointeur vers une variable. Il existe un autre niveau d'indirection, ce qui est un inconvénient; mais il y a aussi un autre niveau de flexibilité, ce qui est un avantage. Ainsi, tout en faisant un choix, vous feriez bien de faire cette analyse des coûts par rapport aux avantages.


17
Donc, (style TDD) vous commenceriez avec les constructeurs comme le moyen le plus simple de faire le travail. Et puis refactoriser en usines une fois que vous commencez à obtenir des odeurs de code (comme une logique conditionnelle répétée déterminant quel constructeur appeler)?
AndyM

1
Point très important. Une étude utilisateur comparant des usines et des constructeurs a trouvé des résultats très significatifs indiquant que les usines sont préjudiciables à la convivialité de l'API: "les utilisateurs ont besoin de beaucoup plus de temps (p = 0,005) pour construire un objet avec une usine qu'avec un constructeur" [The Factory Pattern in API Design : Une évaluation d'utilisabilité ].
mdeff

12

Utilisez une fabrique uniquement lorsque vous avez besoin d'un contrôle supplémentaire avec la création d'objets, d'une manière qui ne pourrait pas être réalisée avec des constructeurs.

Les usines ont la possibilité de mettre en cache par exemple.

Une autre façon d'utiliser les usines est dans un scénario où vous ne connaissez pas le type que vous souhaitez construire. Souvent, vous voyez ce type d'utilisation dans les scénarios d'usine de plugins, où chaque plugin doit dériver d'une classe de base ou implémenter une sorte d'interface. La fabrique crée des instances de classes qui dérivent de la classe de base ou qui implémentent l'interface.


11

Une citation de "Effective Java", 2e éd., Point 1: Considérer les méthodes de fabrique statiques au lieu des constructeurs, p. 5:

"Notez qu'une méthode de fabrique statique n'est pas la même que le modèle de méthode d'usine de Design Patterns [Gamma95, p. 107]. La méthode de fabrique statique décrite dans cet article n'a pas d'équivalent direct dans les modèles de conception."


10

En plus de "java efficace" (comme mentionné dans une autre réponse), un autre livre classique suggère également:

Préférez les méthodes de fabrique statiques (avec des noms qui décrivent les arguments) aux constructeurs surchargés.

Par exemple. n'écris pas

Complex complex = new Complex(23.0);

mais écris plutôt

Complex complex = Complex.fromRealNumber(23.0);

Le livre va jusqu'à suggérer de rendre le Complex(float)constructeur privé, pour forcer l'utilisateur à appeler la méthode de fabrique statique.


2
La lecture de cette partie du livre m'a amené ici
Purple Haze

1
@Bayrem: moi aussi, je l'ai relu récemment et j'ai pensé que je devrais l'ajouter aux réponses.
blue_note

1
Sur le même sujet, vous trouverez peut - être utile certaines conventions de nommage élaborées par le java.time cadre en ce qui concerne la dénomination de from…, to…, parse…, with…et ainsi de suite. Gardez à l'esprit que les classes java.time sont construites pour être immuables, mais certaines de ces conventions de dénomination peuvent également être utiles pour les classes mutables.
Basil Bourque

7

Un exemple concret d'une application CAO / FAO.

Un chemin de coupe serait créé à l'aide d'un constructeur. C'est une série de lignes et d'arcs définissant un chemin à couper. Bien que la série de lignes et d'arcs puisse être différente et avoir des coordonnées différentes, elle est facilement gérée en passant une liste dans un constructeur.

Une forme serait faite en utilisant une usine. Parce qu'il y a une classe de forme, chaque forme serait configurée différemment en fonction du type de forme. Nous ne savons pas quelle forme nous allons initialiser jusqu'à ce que l'utilisateur fasse une sélection.


5

Dites, j'ai un constructeur sur une classe qui attend une valeur id. Le constructeur utilise cette valeur pour remplir la classe à partir de la base de données.

Ce processus doit certainement être en dehors d'un constructeur.

  1. Le constructeur ne doit pas accéder à la base de données.

  2. La tâche et la raison d'un constructeur est d' initialiser les données membres et d' établir un invariant de classe en utilisant des valeurs transmises au constructeur.

  3. Pour tout le reste, une meilleure approche consiste à utiliser une méthode de fabrique statique ou, dans des cas plus complexes, une classe de fabrique ou de constructeur distincte .

Quelques lignes directrices du constructeur de Microsoft :

Faites un travail minimal dans le constructeur. Les constructeurs ne devraient pas faire beaucoup de travail autre que de capturer les paramètres du constructeur. Le coût de tout autre traitement doit être retardé jusqu'à ce qu'il soit nécessaire.

Et

Envisagez d'utiliser une méthode de fabrique statique au lieu d'un constructeur si la sémantique de l'opération souhaitée ne correspond pas directement à la construction d'une nouvelle instance.


2

Parfois, vous devez vérifier / calculer certaines valeurs / conditions lors de la création d'un objet. Et s'il peut lancer une exception, la construction est très mauvaise. Vous devez donc faire quelque chose comme ceci:

var value = new Instance(1, 2).init()
public function init() {
    try {
        doSome()
    }
    catch (e) {
        soAnotherSome()
    }
}

Où tous les calculs supplémentaires sont dans init (). Mais seul vous, en tant que développeur, connaissez vraiment ce init (). Et bien sûr, après des mois, vous l'oubliez. Mais si vous avez une usine - faites tout ce dont vous avez besoin dans une seule méthode en masquant cet init () à l'appel direct - donc pas de problème. Avec cette approche, il n'y a aucun problème avec la création et la fuite de mémoire.

Quelqu'un vous a parlé de la mise en cache. C'est bon. Mais vous devez également vous souvenir du modèle Flyweight qui est agréable à utiliser avec Factory way.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.