Fonction constructeur vs fonctions d'usine

Quelqu'un peut-il clarifier la différence entre une fonction de constructeur et une fonction d'usine en Javascript.

Quand utiliser un au lieu de l'autre?

La différence de base est qu'une fonction de constructeur est utilisée avec le new mot-clé (ce qui provoque JavaScript pour créer automatiquement un nouvel objet, définir this dans la fonction à cet objet et renvoyer l'objet):

 var objFromConstructor = new ConstructorFunction(); 

Une fonction d'usine s'appelle une fonction "régulière":

 var objFromFactory = factoryFunction(); 

Mais pour que cela soit considéré comme une "usine", il faudrait renvoyer une nouvelle instance d'objet: vous ne l'appelez pas une fonction "usine" si elle vient de retourner un boolean ou quelque chose. Cela n'arrive pas automatiquement avec les new , mais cela permet une plus grande flexibilité pour certains cas.

Dans un exemple très simple, les fonctions référencées ci-dessus pourraient ressembler à ceci:

 function ConstructorFunction() { this.someProp1 = "1"; this.someProp2 = "2"; } ConstructorFunction.prototype.someMethod = function() { /* whatever */ }; function factoryFunction() { var obj = { someProp1 : "1", someProp2 : "2", someMethod: function() { /* whatever */ } }; // other code to manipulate obj in some way here return obj; } 

Bien sûr, vous pouvez rendre les fonctions d'usine beaucoup plus compliquées que cet exemple simple.

Certaines personnes préfèrent utiliser les fonctions d'usine pour tout simplement parce qu'elles n'aiment pas avoir à se souvenir de l'utilisation de new (EDIT: et cela peut être un problème car sans new la fonction sera toujours exécutée, mais pas comme prévu). Je ne vois pas cela comme un avantage: le new est une partie essentielle de la langue, de sorte que je l'évite délibérément, c'est un peu arbitraire – pourrait aussi éviter d'autres mots clés comme else .

Un avantage pour les fonctions d'usine est lorsque l'objet à renvoyer peut être de plusieurs types différents en fonction de certains paramètres.

Avantages de l'utilisation de constructeurs

  • La plupart des livres vous enseignent à utiliser les constructeurs et les new

  • this réfère au nouvel objet

  • Certaines personnes aiment la façon dont var myFoo = new Foo(); Lit.

Désavantages

  • Les détails de l'instanciation sont divulgués dans l'API appelante (via la new exigence), de sorte que tous les appelants sont étroitement couplés à l'implémentation du constructeur. Si vous avez besoin d'une flexibilité supplémentaire de l'usine, vous devrez refactoriser tous les appelants (certes, le cas exceptionnel plutôt que la règle).

  • Oublier le new est un bug commun, vous devriez fortement envisager d'ajouter une vérification de vérification pour s'assurer que le constructeur s'appelle correctement ( if (!(this instanceof Foo)) { return new Foo() } ). EDIT: Depuis ES6 (ES2015), vous ne pouvez pas oublier le new avec un constructeur de class , ou le constructeur va lancer une erreur.

  • Si vous faites le contrôle de la vérification, cela laisse l'ambiguïté quant à savoir s'il faut ou non un new . À mon avis, ce ne devrait pas être le cas. Vous avez effectivement court-circuité la new exigence, ce qui signifie que vous pouvez effacer l'inconvénient n ° 1. Mais alors, vous venez d'avoir une fonction d'usine, sauf le nom , avec une boite d'informations supplémentaire, une lettre majuscule et moins flexible dans this contexte.

Les constructeurs rompent le principe ouvert / fermé

Mais ma principale préoccupation est qu'elle viole le principe ouvert / fermé. Vous commencez à exporter un constructeur, les utilisateurs commencent à utiliser le constructeur, puis vous découvrez que vous avez besoin de la flexibilité d'une usine (par exemple, de basculer l'implémentation pour utiliser des pools d'objets ou d'instancier dans des contextes d'exécution ou Ont plus de flexibilité d'héritage en utilisant OO prototypique).

Vous êtes bloqué, cependant. Vous ne pouvez pas effectuer le changement sans briser tout le code qui appelle votre constructeur avec un new . Vous ne pouvez pas passer à l'utilisation de pools d'objets pour des gains de performances, par exemple.

En outre, l'utilisation de constructeurs vous donne une instanceof trompeuse qui ne fonctionne pas dans les contextes d'exécution et ne fonctionne pas si votre prototype est échoué. Il échouera également si vous commencez à le renvoyer à partir de votre constructeur, puis à passer à l'exportation d'un objet arbitraire, que vous devriez faire pour activer le comportement d'usine dans votre constructeur.

Avantages de l'utilisation des usines

  • Moins de code – aucune exigence requise.

  • Vous pouvez renvoyer tout objet arbitraire et utiliser un prototype arbitraire – vous donnant plus de flexibilité pour créer différents types d'objets qui implémentent la même API. Par exemple, un lecteur multimédia qui peut créer des instances à la fois HTML5 et flash players, ou une bibliothèque d'événements pouvant émettre des événements DOM ou des événements de socket web. Les usines peuvent également instancier des objets dans des contextes d'exécution, profiter des pools d'objets et permettre des modèles d'héritage prototypiques plus souples.

  • Vous n'auriez jamais besoin de convertir d'une usine à un constructeur, de sorte que la refactorisation ne sera jamais un problème.

  • Pas d'ambiguïté quant à l'utilisation de new . Ne pas. (Cela fera mal comportement, voir le point suivant).

  • this se comporte comme il le ferait normalement – afin que vous puissiez l'utiliser pour accéder à l'objet parent (par exemple, à l'intérieur de player.create() , this réfère au player , tout comme n'importe quelle autre invocation de méthode. call et apply également réaffecter this , comme prévu Si vous stockez des prototypes sur l'objet parent, cela peut être un excellent moyen d'échanger des fonctionnalités dynamiquement, et de permettre un polymorphisme très flexible pour l'instanciation de votre objet.

  • Pas d'ambiguïté quant à savoir s'il faut ou non capitaliser. Ne pas. Les outils de peluches se plaindront, et vous serez tentés d'essayer d'utiliser de new , puis vous annulerez l'avantage décrit ci-dessus.

  • Certaines personnes aiment la façon dont var myFoo = foo(); Ou var myFoo = foo.create(); Lit.

Désavantages

  • new ne se comporte pas comme prévu (voir ci-dessus). Solution: ne l'utilisez pas.

  • this ne fait pas référence au nouvel objet (au lieu de cela, si le constructeur est invoqué avec une notation de points ou une notation de parenthèse, par exemple foo.bar () – this réfère à foo – tout comme toutes les autres méthodes JavaScript – voir les avantages).

Un constructeur renvoie une instance de la classe dans laquelle vous l'appelez. Une fonction d'usine peut renvoyer n'importe quoi. Vous utiliserez une fonction d'usine lorsque vous devez renvoyer des valeurs arbitraires ou lorsqu'une classe a un grand processus d'installation.

Les usines sont "toujours" meilleures. Lorsque vous utilisez des langues orientées objet, alors

  1. Décider du contrat (les méthodes et ce qu'ils feront)
  2. Créez des interfaces qui exposent ces méthodes (en javascript, vous n'avez pas d'interfaces, donc vous devez trouver un moyen de vérifier la mise en œuvre)
  3. Créez une usine qui renvoie une implémentation de chaque interface requise.

Les implémentations (les objets réels créés avec de nouveaux) ne sont pas exposées à l'utilisateur / consommateur d'usine. Cela signifie que le développeur d'usine peut se développer et créer de nouvelles implémentations tant qu'il ne rompt pas le contrat … et permet au consommateur d'usine de bénéficier de la nouvelle API sans avoir à modifier son code … S'ils ont utilisé une nouvelle et une nouvelle implémentation, ils doivent changer chaque ligne qui utilise "nouveau" pour utiliser la "nouvelle" implémentation … avec l'usine, leur code ne change pas …

Les usines – mieux que toute autre chose – le cadre de printemps est entièrement construit autour de cette idée.

Les usines sont une couche d'abstraction, et, comme toutes les abstractions, elles ont un coût de complexité. Lorsqu'il rencontre une API basée sur l'usine pour déterminer ce que l'usine est pour une API donnée, il peut être difficile pour le consommateur de l'API. Avec la découverte des constructeurs, il est trivial.

Lors de la décision entre les médecins et les usines, vous devez décider si la complexité est justifiée par le bénéfice.

Il convient de noter que les constructeurs Javascript peuvent être des usines arbitraires en renvoyant autre chose que cela ou indéfini. Donc, dans js, vous pouvez obtenir le meilleur des deux mondes: l'API et l'assemblage d'objets et la mise en cache intuitives.