Fermer

avril 21, 2018

JavaScript Design Patterns: Le Singleton –


Dans cet article, nous allons explorer la meilleure façon d'implémenter un singleton en JavaScript, en regardant comment cela a évolué avec la montée de ES6.

Parmi les langages utilisés dans la production à grande échelle, JavaScript est de loin le plus évoluant rapidement, ressemblant moins à ses premières itérations et plus comme Python, avec chaque nouvelle spécification mise en avant par ECMA International. Alors que les changements ont leur juste part de détracteurs, le nouveau JavaScript réussit à rendre le code plus facile à lire et à raisonner, plus facile à écrire d'une manière qui respecte les meilleures pratiques d'ingénierie logicielle (en particulier les concepts de modularité et SOLID)

Explication ES6

ES6 (alias ES2015) a été la première mise à jour majeure du langage depuis la standardisation d'ES5 en 2009. Presque tous les navigateurs modernes supportent ES6 . Cependant, si vous avez besoin d'héberger des navigateurs plus anciens, le code ES6 peut facilement être transmis à ES5 à l'aide d'un outil tel que Babel. ES6 donne à JavaScript une tonne de nouvelles fonctionnalités, y compris une syntaxe supérieure pour les classes et de nouveaux mots-clés pour les déclarations de variables . Vous pouvez en apprendre plus à ce sujet en parcourant des articles SitePoint sur le sujet .

Qu'est-ce qu'un singleton

 9 pièces de puzzle. 8 sont gris, 1 est rouge. Une représentation visuelle du modèle singleton

Dans le cas où vous n'êtes pas familier avec le modèle singleton il s'agit, à la base, d'un modèle de conception qui limite l'instanciation d'une classe à un objet. Habituellement, l'objectif est de gérer l'état de l'application globale. Certains exemples que j'ai vus ou écrits moi-même incluent l'utilisation d'un singleton comme source de paramètres de configuration pour une application Web, du côté client pour tout ce qui est initié avec une clé API (vous ne voulez généralement pas risquer d'envoyer plusieurs appels de suivi analytiques, par exemple), et pour stocker des données en mémoire dans une application web côté client (par exemple, des magasins dans Flux).

Un singleton devrait être immuable par le code consommateur, et il ne devrait y avoir aucun risque d'instancier plus d'un

Note: il y a des scénarios où les singletons peuvent être mauvais, et les arguments qu'ils sont, en fait, toujours mauvais. Pour cette discussion, vous pouvez consulter cet article utile sur le sujet

L'ancienne façon de créer un singleton en JavaScript

L'ancienne façon d'écrire un singleton en JavaScript implique de tirer parti fermetures et expressions de fonction immédiatement appelées . Voici comment nous pourrions écrire un magasin (très simple) pour une implémentation hypothétique de Flux à l'ancienne:

 var UserStore = (function () {
  var _data = [];

  fonction add (item) {
    _data.push (item);
  }

  function get (id) {
    return _data.find ((d) => {
      return d.id === id;
    });
  }

  revenir {
    ajouter: ajouter,
    obtenir
  }
} ());

Lorsque ce code est interprété, UserStore sera défini sur le résultat de cette fonction appelée immédiatement – un objet qui expose deux fonctions, mais qui n'accorde pas un accès direct à la collection de données. [19659008Cependantcecodeestplusverbeuxqu'ilnedevraitl'êtreetnenousdonnepasnonplusl'immuabilitéquenousdésironsenutilisantdessingletonsLecodeexécutéplustardpeutmodifierl'unedesfonctionsexposéesvoiremêmeredéfinir UserStore . De plus, le code modifiant / offensant pourrait être n'importe où! Si nous avions des bugs suite à une modification inattendue du UsersStore les repérer dans un projet plus vaste pourrait s'avérer très frustrant.

Il y a des mouvements plus avancés que vous pourriez faire pour atténuer certains de ces inconvénients. spécifié dans cet article par Ben Cherry. (Son but est de créer des modules, qui sont simplement des singletons, mais le modèle est le même.) Mais ceux-ci ajoutent une complexité inutile au code, tout en ne réussissant pas à nous obtenir exactement ce que nous voulons

The New Way ( s)

En tirant parti des fonctionnalités ES6, principalement des modules et de la nouvelle déclaration de variables const nous pouvons écrire des singletons d'une manière non seulement plus concise, mais qui répond mieux à nos besoins. avec la mise en œuvre la plus élémentaire. Voici une interprétation moderne (plus propre et plus puissante) de l'exemple ci-dessus:

 const _data = [];

const UserStore = {
  ajouter: item => _data.push (item),
  get: id => _data.find (d => d.id === id)
}

Object.freeze (UserStore);
exporter le UserStore par défaut;

Comme vous pouvez le voir, cette façon offre une amélioration de la lisibilité. Mais là où il brille vraiment, c'est dans la contrainte imposée au code qui consomme notre petit module singleton ici: le code consommateur ne peut pas réaffecter UserStore à cause du mot clé const . Et à la suite de notre utilisation de Object.freeze ses méthodes ne peuvent pas être changées, ni de nouvelles méthodes ou propriétés ne peuvent y être ajoutées. De plus, parce que nous profitons des modules ES6, nous savons exactement où UserStore est utilisé

Maintenant, nous avons créé un UserStore littéral d'objet. La plupart du temps, aller avec un objet littéral est l'option la plus lisible et la plus concise. Cependant, il y a des moments où vous pourriez vouloir exploiter les avantages d'aller avec une classe traditionnelle. Par exemple, les magasins dans Flux auront tous les mêmes fonctionnalités de base. Tirer parti de l'héritage orienté objet traditionnel est un moyen d'obtenir cette fonctionnalité répétitive tout en gardant votre code DRY.

Voici comment l'implémentation ressemblerait si nous voulions utiliser les classes ES6:

 class UserStore {
  constructeur(){
    this._data = [];
  }

  ajouter un item){
    this._data.push (item);
  }

  get (id) {
    renvoie this._data.find (d => d.id === id);
  }
}

const instance = nouveau UserStore ();
Object.freeze (instance);

exporter l'instance par défaut;

Cette façon de faire est légèrement plus verbeuse que l'utilisation d'un littéral d'objet, et notre exemple est si simple que nous ne voyons vraiment aucun avantage à utiliser une classe (bien que ce soit utile dans le dernier exemple).

Un avantage pour la route de classe qui peut ne pas être évident est que, s'il s'agit de votre code frontal, et que votre back-end est écrit en C # ou Java, vous pouvez utiliser les mêmes motifs de conception dans votre application côté client. comme vous le faites sur le backend, et augmentez l'efficacité de votre équipe (si vous êtes petit et que les gens travaillent en full stack). Cela semble doux et difficile à mesurer, mais je l'ai expérimenté de première main en travaillant sur une application C # avec un frontal React, et le bénéfice est réel.

Il convient de noter que, techniquement, l'immuabilité et la non-dépassabilité du singleton en utilisant ces deux modèles peut être subverti par le provocateur motivé. Un objet littéral peut être copié, même s'il est lui-même const en utilisant Object.assign . Et lorsque nous exportons une instance d'une classe, bien que nous n'exposons pas directement la classe elle-même au code consommateur, le constructeur de n'importe quelle instance est disponible en JavaScript et peut être appelé pour créer de nouvelles instances. Évidemment, cependant, tout cela demande au moins un peu d'effort, et j'espère que vos collègues devins ne sont pas aussi insistants pour violer le modèle singleton.

Mais disons que vous vouliez être sûr que personne ne plaisante avec l'unicité de votre singleton, et vous vouliez aussi qu'il soit encore plus proche de l'implémentation des singletons dans le monde orienté objet. Voici ce que vous pouvez faire:

 class UserStore {
  constructeur(){
   if (! UserStore.instance) {
     this._data = [];
     UserStore.instance = ceci;
   }

   retourne UserStore.instance;
  }

 // reste le même code que l'exemple précédent

}

const instance = nouveau UserStore ();
Object.freeze (instance);

exporter l'instance par défaut;

En ajoutant l'étape supplémentaire de contenir une référence à l'instance, nous pouvons vérifier si nous avons déjà instancié un UserStore et si nous en avons, nous n'en créerons pas un nouveau. Comme vous pouvez le voir, cela fait également bon usage du fait que UserStore est une classe.

Pensées? Hate Mail?

Il y a sans aucun doute beaucoup de développeurs qui utilisent l'ancien modèle singleton / module en JavaScript depuis plusieurs années, et qui trouvent que ça marche plutôt bien pour eux. Néanmoins, parce que trouver de meilleures façons de faire est si essentiel à l'esprit d'être un développeur, nous espérons que des modèles plus propres et plus faciles à raisonner, comme celui-ci, gagneront de plus en plus en popularité. Particulièrement quand il devient plus facile et plus banal d'utiliser les fonctionnalités ES6 +.

C'est un modèle que j'ai utilisé en production pour construire les magasins dans une implémentation personnalisée de Flux (magasins qui avaient un peu plus de succès que nos exemples ici) et ça a bien marché. Mais si vous pouvez voir des trous, s'il vous plaît faites le moi savoir. Aussi s'il vous plaît défendre pour les nouveaux modèles que vous préférez, et si vous pensez que les littéraux d'objet sont la voie à suivre, ou si vous préférez les classes!




Source link