168 votes

Fonction constructeur et fonctions usine

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

Quand utiliser l'un plutôt que l'autre ?

170voto

nnnnnn Points 70578

La différence fondamentale est qu'une fonction constructeur est utilisée avec l'option new (qui fait en sorte que JavaScript crée automatiquement un nouvel objet, définit this à l'intérieur de la fonction à cet objet, et retourner l'objet) :

var objFromConstructor = new ConstructorFunction();

Une fonction d'usine est appelée comme une fonction "ordinaire" :

var objFromFactory = factoryFunction();

Mais pour qu'elle soit considérée comme une "usine", il faudrait qu'elle renvoie une nouvelle instance d'un objet : vous ne l'appelleriez pas une fonction "usine" si elle ne renvoyait qu'un booléen ou autre. Cela ne se produit pas automatiquement comme avec new mais elle permet une plus grande flexibilité dans 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 ce simple exemple.

Les fonctions d'usine présentent un avantage lorsque l'objet à renvoyer peut être de plusieurs types différents en fonction de certains paramètres.

18 votes

"(EDIT : et cela peut être un problème car sans new la fonction s'exécutera quand même mais pas comme prévu)." Ce n'est un problème que si vous essayez d'appeler une fonction de fabrique avec "new" ou si vous essayez d'utiliser le mot-clé "this" pour assigner à l'instance. Sinon, vous créez simplement un nouvel objet arbitraire et vous le renvoyez. Il n'y a pas de problème, c'est juste une façon différente et plus flexible de faire les choses, avec moins de texte passe-partout, et sans laisser fuir les détails de l'instanciation dans l'API.

0 votes

Notez que renvoyer un objet depuis la fabrique n'a pas vraiment de sens. Renvoyer une fonction depuis la factory aurait un sens car les paramètres pourraient être pré-calculés (par exemple RegExp pourrait être compilé) et passés dans la fonction qui est renvoyée...

0 votes

@Nux - Renvoyer un objet à partir d'une fonction d'usine est parfaitement logique et est une chose courante à faire. (Évidemment, vous auriez normalement quelque chose de plus compliqué qu'un couple de propriétés codées en dur comme dans mon exemple).

126voto

Eric Elliott Points 948

Avantages de l'utilisation des constructeurs

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

  • this fait référence au nouvel objet

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

Inconvénients

  • Les détails de l'instanciation sont divulgués dans l'API appelante (par l'intermédiaire de la fonction new ), de sorte que tous les appelants sont étroitement liés à l'implémentation du constructeur. Si vous avez un jour besoin de la flexibilité supplémentaire de la fabrique, vous devrez remanier tous les appelants (il est vrai que c'est un cas exceptionnel, plutôt que la règle).

  • Oublier new est un bogue si courant que vous devriez envisager d'ajouter une vérification de type "boilerplate" pour vous assurer que le constructeur est appelé correctement ( if (!(this instanceof Foo)) { return new Foo() } ). EDIT : Depuis ES6 (ES2015) vous ne pouvez pas oublier new avec un class ou le constructeur lancera une erreur.

  • Si vous faites le instanceof le contrôle, il laisse une ambiguïté quant à savoir si oui ou non new est nécessaire. A mon avis, ça ne devrait pas l'être. Vous avez effectivement court-circuité le new ce qui signifie que vous pouvez éliminer l'inconvénient n°1. Mais alors vous avez juste une fonction d'usine, sauf le nom. avec un texte passe-partout supplémentaire, une lettre majuscule et moins de souplesse. this le contexte.

Les constructeurs rompent le principe d'ouverture/fermeture

Mais ma principale préoccupation est que cela viole le principe d'ouverture/fermeture. Vous commencez par exporter un constructeur, les utilisateurs commencent à utiliser le constructeur, puis vous réalisez que vous avez besoin de la flexibilité d'une usine, à la place (par exemple, pour changer l'implémentation pour utiliser des pools d'objets, ou pour instancier à travers les contextes d'exécution, ou pour avoir plus de flexibilité d'héritage en utilisant l'OO prototypique).

Vous êtes coincé, cependant. Vous ne pouvez pas faire le changement sans casser tout le code qui appelle votre constructeur avec new . Vous ne pouvez pas passer à l'utilisation de pools d'objets pour gagner en performance, par exemple.

De plus, l'utilisation de constructeurs vous donne une trompeuse instanceof qui ne fonctionne pas à travers les contextes d'exécution, et qui ne fonctionne pas si votre prototype de constructeur est échangé. Il échouera également si vous commencez par retourner this de votre constructeur, et ensuite passer à l'exportation d'un objet arbitraire, ce que vous devriez faire pour permettre un comportement de type usine dans votre constructeur.

Avantages de l'utilisation des usines

  • Moins de code - aucun modèle standard n'est nécessaire.

  • Vous pouvez renvoyer n'importe quel objet arbitraire et utiliser n'importe quel prototype arbitraire, ce qui vous donne plus de flexibilité pour créer différents types d'objets qui mettent en œuvre la même API. Par exemple, un lecteur multimédia qui peut créer des instances de lecteurs HTML5 et flash, ou une bibliothèque d'événements qui peut émettre des événements DOM ou des événements web socket. Les fabriques peuvent également instancier des objets dans plusieurs contextes d'exécution, tirer parti des pools d'objets et permettre des modèles d'héritage prototypiques plus souples.

  • Vous n'aurez jamais besoin de passer d'une fabrique à un constructeur, et la refactorisation ne sera donc jamais un problème.

  • Aucune ambiguïté quant à l'utilisation new . Ne le faites pas. (Cela fera this se comportent mal, voir le point suivant).

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

  • Il n'y a pas d'ambiguïté sur la nécessité de mettre une majuscule ou non. Ne le faites pas. Les outils Lint se plaindront, et vous serez alors tenté d'essayer d'utiliser new et vous annulerez alors l'avantage décrit ci-dessus.

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

Inconvénients

  • 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 contraire, si le constructeur est invoqué avec la notation par points ou la notation par crochets, par exemple foo.bar() - this se réfère à foo - comme toutes les autres méthodes JavaScript (voir les avantages).

2 votes

Dans quel sens voulez-vous dire que les constructeurs rendent les appelants étroitement couplés à leur implémentation ? En ce qui concerne les arguments du constructeur, ils devront être passés même à la fonction de fabrique pour qu'elle les utilise et invoque le constructeur approprié à l'intérieur.

0 votes

Dans le sens où les appelants doivent utiliser new pour les constructeurs, mais ne peuvent pas utiliser new pour les usines. Voir la nouvelle section sur le principe ouvert/fermé dans ma réponse pour plus de détails.

4 votes

En ce qui concerne la violation de Open/Closed : ne s'agit-il pas de l'injection de dépendances ? Si A a besoin de B, que A appelle new B() ou que A appelle BFactory.create(), les deux introduisent un couplage. Par contre, si vous donnez à A une instance de B dans la composition Root, A n'a pas besoin de savoir comment B est instancié. Je pense que les constructeurs et les fabriques ont tous deux leur utilité ; les constructeurs sont destinés aux instanciations simples, les fabriques aux instanciations plus complexes. Mais dans les deux cas, l'injection de vos dépendances est judicieuse.

40voto

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

9voto

Traktor53 Points 1375

Un exemple de fonction Constructor

function User(name) {
  this.name = name;
  this.isAdmin = false;
}

let user = new User("Jack");
  • new crée un objet prototypé sur User.prototype et appelle User avec l'objet créé comme son this valeur.

  • new traite une expression d'argument pour son opérande comme facultative :

         let user = new User;

    causerait new d'appeler User sans aucun argument.

  • new retourne l'objet qu'il a créé, sauf si le constructeur retourne une valeur d'objet qui est retourné à la place. Il s'agit d'un cas limite qui, dans la plupart des cas, peut être ignoré.

Avantages et inconvénients

Les objets créés par les fonctions du constructeur héritent des propriétés de l'objet du constructeur. prototype et renvoie la valeur vraie en utilisant la propriété instanceOf sur la fonction constructeur.

Les comportements ci-dessus peuvent échouer si vous changez dynamiquement la valeur de l'élément de construction prototype après avoir déjà utilisé le constructeur. Il est rare que cela se produise. et il ne peut pas être modifié si le constructeur a été créé à l'aide de l'attribut class mot-clé.

Les fonctions de constructeur peuvent être étendues à l'aide de la fonction extends mot-clé.

Les fonctions constructives ne peuvent pas retourner null comme valeur d'erreur. Puisqu'il ne s'agit pas d'un type de données objet, il est ignoré par new .

Un exemple de fonction d'usine

function User(name, age) {
  return {
    name,
    age,
  }
};

let user = User("Tom", 23);

Ici, la fonction d'usine est appelée sans new . La fonction est entièrement responsable de l'utilisation directe ou indirecte de ses arguments et du type d'objet qu'elle renvoie. Dans cet exemple, elle renvoie un simple [objet objet] avec certaines propriétés définies à partir des arguments.

Avantages et inconvénients

Permet de masquer facilement les complexités de mise en œuvre de la création d'objets à l'appelant. Ceci est particulièrement utile pour les fonctions de code natif dans un navigateur.

La fonction de fabrique ne doit pas toujours renvoyer des objets du même type, et peut même renvoyer null comme indicateur d'erreur.

Dans les cas simples, les fonctions d'usine peuvent être simples dans leur structure et leur signification.

Les objets renvoyés n'héritent généralement pas de la fonction d'usine. prototype et renvoyer false de instanceOf factoryFunction .

La fonction d'usine ne peut pas être étendue en toute sécurité en utilisant la fonction extends car les objets étendus hériteraient des fonctions de l'usine. prototype plutôt qu'à partir de la propriété prototype du constructeur utilisé par la fonction d'usine.

1 votes

Il s'agit d'une réponse tardive postée en réponse à cette question sur le même sujet,

0 votes

Non seulement "null", mais le "nouveau" ignorera également tout type de données prémitif retourné par la fonction contructeur.

3voto

Colin Saxton Points 49

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

  1. décider du contrat (les méthodes et ce qu'elles vont faire)
  2. Créer des interfaces qui exposent ces méthodes (en javascript, il n'y a pas d'interfaces, il faut donc trouver un moyen de vérifier l'implémentation).
  3. Créez une fabrique qui renvoie une mise en œuvre de chaque interface requise.

Les implémentations (les objets réels créés avec new) ne sont pas exposées à l'utilisateur/consommateur de la fabrique. Cela signifie que le développeur de la fabrique peut s'étendre et créer de nouvelles implémentations tant qu'il/elle ne rompt pas le contrat... et cela permet au consommateur de la fabrique de bénéficier de la nouvelle API sans avoir à modifier son code... s'il utilise new et qu'une "nouvelle" implémentation arrive, il doit modifier chaque ligne qui utilise "new" pour utiliser la "nouvelle" implémentation... avec la fabrique, son code ne change pas...

Les usines - mieux que tout autre chose - le framework Spring est entièrement construit autour de cette idée.

0 votes

Comment l'usine résout-elle ce problème de devoir changer chaque ligne ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X