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 ?
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 ?
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.
"(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.
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...
@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).
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.
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.
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.
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.
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).
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.
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.
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.
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é.
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
.
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.
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.
Les usines sont "toujours" meilleures. Lorsque vous utilisez des langages orientés objet, alors
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.
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.