web-dev-qa-db-fra.com

Fonction constructeur vs fonctions usine

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

Quand utiliser l'un au lieu de l'autre?

135
Sinan

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

var objFromConstructor = new ConstructorFunction();

Une fonction d'usine s'appelle une fonction "normale":

var objFromFactory = factoryFunction();

Mais pour que cela soit considéré comme une "usine", il faudrait qu'il retourne une nouvelle instance d'un objet: vous ne l'appelleriez pas une fonction "usine" si elle venait de renvoyer un booléen ou quelque chose du genre. Cela ne se produit pas automatiquement comme avec new, mais cela permet plus de 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.

Certaines personnes préfèrent utiliser les fonctions d'usine pour tout simplement parce qu'elles n'aiment pas avoir à penser à utiliser 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: new est une partie essentielle du langage, donc, l’éviter délibérément est un peu arbitraire - pourrait aussi éviter d’autres mots-clés tels que else.

L'un des avantages des fonctions d'usine est que l'objet à renvoyer peut être de différents types en fonction de certains paramètres.

140
nnnnnn

Avantages de l'utilisation de 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.

Désavantages

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

  • Oublier new est un bug si courant, vous devriez fortement envisager d’ajouter une vérification standard afin de s’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 constructeur class, sinon le constructeur émettra une erreur.

  • Si vous effectuez la vérification instanceof, cela laisse une ambiguïté quant à savoir si new est requis ou non. À mon avis, ça ne devrait pas l'être. Vous avez effectivement court-circuité l'exigence new, ce qui signifie que vous pouvez effacer l'inconvénient n ° 1. Mais alors vous avez juste une fonction d’usine dans tout sauf nom, avec un passe-partout supplémentaire, une lettre majuscule et un contexte moins flexible this.

Les constructeurs brisent 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 à l’utiliser, puis vous réalisez que vous avez besoin de la flexibilité d’une fabrique (par exemple, pour changer l’implémentation afin d’utiliser des pools d’objets, pour instancier des contextes d’exécution, ou pour avoir plus de flexibilité en matière d’héritage en utilisant un OO prototype).

Vous êtes coincé, cependant. Vous ne pouvez pas effectuer le changement sans détruire tout le code qui appelle votre constructeur avec new. Par exemple, vous ne pouvez pas utiliser des pools d'objets pour des gains de performances.

En outre, l'utilisation de constructeurs vous donne un instanceof trompeur qui ne fonctionne pas dans les contextes d'exécution et qui ne fonctionne pas si votre prototype de constructeur est échangé. Cela échouera également si vous commencez par renvoyer this à partir de votre constructeur, puis basculez vers l'exportation d'un objet arbitraire, ce que vous devez faire pour activer un comportement de type fabrique dans votre constructeur.

Avantages de l'utilisation des usines

  • Moins de code - pas de passe-partout.

  • Vous pouvez renvoyer n'importe quel objet arbitraire et utiliser n'importe quel prototype, ce qui vous donne plus de flexibilité pour créer différents types d'objets qui implémentent la même API. Par exemple, un lecteur multimédia pouvant créer des instances de lecteurs HTML5 et flash, 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, tirer parti des pools d'objets et permettre des modèles d'héritage prototypiques plus souples.

  • Vous n'auriez jamais besoin de convertir une usine en un constructeur, le refactoring ne sera donc jamais un problème.

  • Aucune ambiguïté sur l'utilisation de new. Ne pas (Cela fera que this se comporte mal, voir le point suivant).

  • this se comporte normalement - vous pouvez donc l'utiliser pour accéder à l'objet parent (par exemple, à l'intérieur de player.create(), this fait référence à player, Tout comme n'importe quelle autre invocation de méthode. call et apply réaffectent également this, comme prévu. Si vous stockez des prototypes sur l'objet parent, cela peut être un excellent moyen de dynamiser permutez les fonctionnalités et activez un polymorphisme très flexible pour votre instanciation d'objet.

  • Aucune ambiguïté sur l'opportunité de capitaliser ou non. Ne pas Les outils anti-peluche se plaindront et vous serez ensuite tenté d'essayer d'utiliser new, puis d'annuler 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 appelé avec une notation en points ou en crochets, par exemple, foo.bar () - this fait référence à foo - comme toute autre méthode JavaScript - voir avantages).

102
Eric Elliott

Un constructeur renvoie une instance de la classe sur laquelle vous l'appelez. Une fonction d'usine peut renvoyer n'importe quoi. Vous utiliseriez une fonction fabrique lorsque vous devez renvoyer des valeurs arbitraires ou lorsqu'une classe est soumise à un processus d'installation volumineux.

38

Un exemple de fonction constructeur

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 valeur this.

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

       let user = new User;
    

    provoquerait new à appeler User sans arguments.

  • new renvoie l'objet qu'il a créé, à moins que le constructeur ne retourne une valeur d'objet, qui est retourné à la place. C'est un cas Edge qui peut être ignoré pour la plupart.

Avantages et inconvénients

Les objets créés par les fonctions du constructeur héritent des propriétés de la propriété prototype du constructeur et renvoient true à l'aide de l'opérateur instanceOf de la fonction constructeur.

Les comportements ci-dessus peuvent échouer si vous modifiez de manière dynamique la valeur de la propriété prototype du constructeur après avoir déjà utilisé le constructeur. Cela est rare, et cela ne peut pas être changé si le constructeur a été créé avec le mot clé class.

Les fonctions de constructeur peuvent être étendues en utilisant le mot clé extends.

Les fonctions de constructeur ne peuvent pas renvoyer null en tant que valeur d'erreur. Comme il ne s'agit pas d'un type de données d'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 usine est appelée sans new. La fonction est entièrement responsable de l'utilisation directe ou indirecte si ses arguments et le type d'objet qu'elle renvoie. Dans cet exemple, il retourne un simple [objet Object] avec des propriétés définies à partir d'arguments.

Avantages et inconvénients

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

La fonction usine ne doit pas toujours renvoyer des objets du même type et peut même renvoyer null en tant qu'indicateur d'erreur.

Dans des cas simples, les fonctions d'usine peuvent être simples en termes de structure et de signification.

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

La fonction factory ne peut pas être étendue en toute sécurité à l'aide du mot clé extends, car les objets étendus hériteraient des propriétés fabrique prototype au lieu de la propriété prototype du constructeur utilisé par la fonction d'usine.

5
traktor53

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 vont faire)
  2. Créez des interfaces qui exposent ces méthodes (en javascript, vous n'avez pas d'interface, vous devez donc trouver un moyen de vérifier l'implémentation)
  3. Créez une fabrique qui renvoie une implémentation de chaque interface requise.

Les implémentations (les objets réels créés avec new) ne sont pas exposées à l'utilisateur/consommateur de l'usine. Cela signifie que le développeur d'usine peut développer et créer de nouvelles implémentations tant qu'il/elle ne rompt pas le contrat ... et cela permet au consommateur d'usine de profiter de la nouvelle API sans avoir à changer de code ... s'ils utilisent new et qu'une "nouvelle" implémentation se présente, ils doivent changer chaque ligne qui utilise "new" 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 complètement construit autour de cette idée.

2
Colin Saxton

Pour les différences, Eric Elliott a très bien précisé,

Mais pour la deuxième question:

Quand utiliser l'un au lieu de l'autre?

Si vous venez de l'arrière-plan orienté objet, la fonction constructeur vous semble plus naturelle. De cette façon, vous ne devriez pas oublier d'utiliser le mot clé new.

0
Mostafa

Les usines sont une couche d'abstraction et, comme toutes les abstractions, elles ont une complexité. Lorsque vous rencontrez une API basée sur une usine, déterminer ce qu’elle est pour une API donnée peut être difficile pour le consommateur d’API. Avec les constructeurs, la découvrabilité est triviale.

Lorsque vous décidez entre acteurs et usines, vous devez décider si la complexité est justifiée par le bénéfice.

Il est à noter que les constructeurs Javascript peuvent être des usines arbitraires en renvoyant autre chose que ceci ou indéfini. Donc, dans js, vous pouvez obtenir le meilleur des deux mondes: une API et une mise en pool/mise en cache d’objets détectables.

0
PeterH