web-dev-qa-db-fra.com

React Native - L'utilisation d'un singleton est-elle la meilleure alternative à DI?

J'ai beaucoup lu sur le modèle singleton et comment il est "mauvais" car il rend les classes l'utilisant difficile à tester, donc il faut l'éviter. J'ai lu quelques articles expliquant comment le singleton pourrait être remplacé par l'injection de dépendance, mais cela me semble inutilement complexe.

Voici mon problème un peu plus en détail. Je crée une application mobile en utilisant React Native et je veux créer un client REST qui communiquera avec le serveur, obtiendra des données, publiera des données et traitera la connexion (stocker le jeton de connexion et l'envoyer à chaque demande après la connexion).

Mon plan initial était de créer un objet singleton (RESTClient) que mon application utilisera initialement pour se connecter, puis faire une demande d'envoi des informations d'identification si nécessaire. L'approche DI me semble vraiment compliquée (peut-être parce que je n'ai jamais utilisé DI auparavant) mais j'utilise ce projet pour en apprendre le plus possible, donc je veux faire ce qui est le mieux ici. Toutes suggestions et commentaires sont très appréciés.

Edit: J'ai maintenant réalisé que j'avais mal formulé ma question. Je voulais des conseils sur la façon d'éviter le motif singleton dans RN et devrais-je même le faire. Heureusement, Samuel m'a donné le genre de réponse que je voulais. Mon problème était que je voulais éviter le modèle singleton et utiliser DI, mais il semblait vraiment compliqué de l'implémenter en React Native. J'ai fait d'autres recherches et l'ai implémenté en utilisant le système de contexte Reacts.

Pour toute personne intéressée, voici comment je l'ai fait. Comme je l'ai dit, j'ai utilisé le contexte dans RN qui est quelque chose comme des accessoires, mais il se propage à chaque composant.

Dans le composant racine, je fournis les dépendances nécessaires comme ceci:

export default class Root extends Component {
  getChildContext() {
    restClient: new MyRestClient();
  }

  render() {...}
}
Root.childContextTypes = {restClient: PropTypes.object};

RestClient est désormais disponible dans tous les composants sous Root. Je peux y accéder comme ça.

export default class Child extends Component {
  useRestClient() {
    this.context.restClient.getData(...);
  }

  render() {...}
}
Child.contextTypes = {restClient: PropTypes.object}

Cela éloigne efficacement la création d'objets de la logique et dissocie l'implémentation client REST de mes composants).

8
Mateo Hrastnik

L'injection de dépendance n'a pas besoin d'être complexe du tout, et cela vaut vraiment la peine d'apprendre et d'utiliser. Habituellement, c'est compliqué par l'utilisation de frameworks d'injection de dépendances, mais ils ne sont pas nécessaires.

Dans sa forme la plus simple, l'injection de dépendances consiste à transmettre des dépendances au lieu de les importer ou de les construire. Cela peut être implémenté en utilisant simplement un paramètre pour ce qui serait importé. Disons que vous avez un composant nommé MyList qui doit utiliser RESTClient pour récupérer des données et les afficher à l'utilisateur. L'approche "singleton" ressemblerait à ceci:

import restClient from '...' // import singleton

class MyList extends React.Component {
  // use restClient and render stuff
}

Ceci associe étroitement MyList à restClient, et il n'y a aucun moyen de tester les unités MyList sans tester restClient. L'approche DI ressemblerait à ceci:

function MyListFactory(restClient) {
  class MyList extends React.Component {
    // use restClient and render stuff
  }

  return MyList
}

C'est tout ce qu'il faut pour utiliser DI. Il ajoute au plus deux lignes de code et vous éliminez une importation. La raison pour laquelle j'ai introduit une nouvelle fonction "usine" est parce que AFAIK vous ne pouvez pas passer de paramètres de constructeur supplémentaires dans React, et je préfère ne pas passer ces choses à travers React propriétés parce que ce n'est pas portable et tous les composants parents doivent savoir transmettre tous les accessoires aux enfants.

Alors maintenant, vous avez une fonction pour construire des composants MyList, mais comment l'utilisez-vous? Le modèle DI bouillonne dans la chaîne de dépendance. Supposons que vous ayez un composant MyApp qui utilise MyList. L'approche "singleton" serait:

import MyList from '...'
class MyApp extends React.Component {
  render() {
    return <MyList />
  }
}

L'approche DI est la suivante:

function MyAppFactory(ListComponent) {
  class MyApp extends React.Component {
    render() {
      return <ListComponent />
    }
  }

  return MyApp
}

Nous pouvons maintenant tester MyApp sans tester MyList directement. Nous pourrions même réutiliser MyApp avec un type de liste complètement différent. Ce modèle bouillonne jusqu'à la racine de la composition . C'est là que vous appelez vos usines et câblez tous les composants.

import RESTClient from '...'
import MyListFactory from '...'
import MyAppFactory from '...'

const restClient = new RESTClient(...)
const MyList = MyListFactory(restClient)
const MyApp = MyAppFactory(MyList)

ReactDOM.render(<MyApp />, document.getElementById('app'))

Maintenant, notre système utilise une seule instance de RESTClient, mais nous l'avons conçue de manière à ce que les composants soient couplés de manière lâche et faciles à tester.

15
Samuel

Selon vos prémisses (apprentissage), la réponse la plus simple est non, les singletons ne sont pas la meilleure alternative à l'injection de dépendance .

Si l'apprentissage est l'objectif, vous trouverez que DI est une ressource plus précieuse dans votre boîte à outils que Singleton. Cela peut sembler complexe, mais la courbe d'apprentissage concerne presque tout ce que vous devez apprendre à partir de zéro. Ne vous allongez pas sur votre zone de confort ou il n'y aura pas d'apprentissage du tout.

Techniquement, il y a peu de différence entre singleton et instance unique (ce que je pense que vous essayez de faire). En apprenant DI, vous vous rendrez compte que vous pouvez injecter des instances uniques partout dans le code. Vous trouverez que votre code est plus facile à tester et peu couplé. ( Pour plus de détails, consultez Réponse de Samuel)

Mais ne vous arrêtez pas ici. Implémentez le même code avec Singleton. Comparez ensuite les deux approches.

La compréhension et la familiarité avec les deux implémentations vous permettront de savoir quand elles sont appropriées et vous serez probablement en mesure de répondre vous-même à la question.

C'est maintenant, pendant la formation, que vous forgez votre Golden Hammers, donc si vous décidez d'éviter d'apprendre DI, il est probable que vous finirez par implémenter des singletons à chaque fois que vous besoin DI.

5
Laiv

Je suis d'accord avec les autres réponses selon lesquelles apprendre ce qu'est DI et comment l'utiliser est une bonne idée.

Cela dit, l'avertissement que les singletons rendent les tests trop difficiles est généralement émis par des personnes utilisant langages typés statiquement (C++, C #, Java, etc.).
En revanche, dans un langage dynamique (Javascript, PHP, Python, Ruby, etc.), il n'est généralement pas plus difficile de remplacer un singleton par une implémentation spécifique au test qu'il ne le serait. dans le cas où vous utilisez DI.

Dans ce cas, je recommande d'utiliser le design qui semble plus naturel pour vous et vos co-développeurs, car cela aura tendance à éviter les erreurs. Si cela se traduit par des singletons, tant pis.

(Mais là encore: apprenez DI avant de prendre cette décision.)

0
Lutz Prechelt