Native reagieren - Ist die Verwendung eines Singletons die beste Alternative zu DI?

8

Ich habe viel über das Singleton-Muster gelesen und wie es "schlecht" ist, weil es die Klassen, die es verwenden, schwer zu testen macht, so dass es vermieden werden sollte. Ich habe einige Artikel gelesen, in denen erklärt wird, wie der Singleton durch Abhängigkeitsinjektion ersetzt werden kann, aber er erscheint mir unnötig komplex.

Hier ist mein Problem etwas detaillierter. Ich erstelle eine mobile App mit React Native und möchte einen REST-Client erstellen, der mit dem Server kommuniziert, Daten abruft, Daten veröffentlicht und die Anmeldung verarbeitet (speichern Sie das Anmeldetoken und senden Sie es bei jeder Anforderung nach der Anmeldung).

Mein ursprünglicher Plan war es, ein Singleton-Objekt (RESTClient) zu erstellen, mit dem sich meine App zunächst anmeldet und dann bei Bedarf die Anmeldeinformationen sendet. Der DI-Ansatz scheint mir wirklich kompliziert zu sein (vielleicht, weil ich DI noch nie zuvor verwendet habe), aber ich verwende dieses Projekt, um so viel wie möglich zu lernen, damit ich hier das Beste tun möchte. Anregungen und Kommentare werden sehr geschätzt.

Bearbeiten: Ich habe jetzt festgestellt, dass ich meine Frage schlecht formuliert habe. Ich wollte eine Anleitung, wie man das Singleton-Muster in RN vermeidet und sollte ich es überhaupt tun. Zum Glück gab mir Samuel genau die Antwort, die ich wollte. Mein Problem war, dass ich das Singleton-Muster vermeiden und DI verwenden wollte, aber es schien wirklich kompliziert, es in React Native zu implementieren. Ich habe weitere Nachforschungen angestellt und es mit dem Reacts-Kontextsystem implementiert.

Für alle Interessierten hier ist, wie ich es gemacht habe. Wie gesagt, ich habe den Kontext in RN verwendet, der so etwas wie Requisiten ist, aber auf jede Komponente übertragen wird.

In der Root-Komponente stelle ich die erforderlichen Abhängigkeiten wie folgt bereit:

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

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

Jetzt ist restClient in allen Komponenten unter Root verfügbar. Ich kann so darauf zugreifen.

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

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

Dadurch wird die Objekterstellung effektiv von der Logik entfernt und die REST-Client-Implementierung von meinen Komponenten entkoppelt.

Mateo Hrastnik
quelle
3
Überlegen Sie, ob Sie den Titel der Frage ändern möchten. Im Moment sind REST und Client irrelevante Elemente. Die einzige Frage hier ist: Ist Singleton in meiner spezifischen Situation die beste Alternative zu DI? .
Laiv
Ich weiß nicht viel über reagieren, aber im Allgemeinen ist DI ein einfaches Konzept. Wenn Sie es komplex finden, haben Sie vermutlich keine gute Erklärung gelesen / gehört.
Ben Aaronson

Antworten:

15

Die Abhängigkeitsinjektion muss überhaupt nicht komplex sein und es lohnt sich absolut, sie zu lernen und anzuwenden. Normalerweise wird es durch die Verwendung von Abhängigkeitsinjektions-Frameworks kompliziert, aber sie sind nicht erforderlich.

In der einfachsten Form übergibt die Abhängigkeitsinjektion Abhängigkeiten, anstatt sie zu importieren oder zu konstruieren. Dies kann durch einfaches Verwenden eines Parameters für das, was importiert werden soll, implementiert werden. Nehmen wir an, Sie haben eine Komponente mit dem Namen MyList, mit RESTClientder einige Daten abgerufen und dem Benutzer angezeigt werden müssen. Der "Singleton" -Ansatz würde ungefähr so ​​aussehen:

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

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

Diese eng Paare MyListzu restClient, und es gibt keine Möglichkeit, Unit - Test kann MyListohne Prüfung restClient. Der DI-Ansatz würde ungefähr so ​​aussehen:

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

  return MyList
}

Das ist alles was man braucht um DI zu benutzen. Es werden höchstens zwei Codezeilen hinzugefügt, und Sie können einen Import eliminieren. Der Grund, warum ich eine neue Funktion "factory" eingeführt habe, ist, dass AFAIK Sie keine zusätzlichen Konstruktorparameter in React übergeben können. Ich ziehe es vor, diese Dinge nicht über die React-Eigenschaften weiterzugeben, da sie nicht portierbar sind und alle übergeordneten Komponenten wissen müssen, um alle zu übergeben Requisiten für Kinder.

Jetzt haben Sie eine Funktion zum Erstellen von MyListKomponenten, aber wie verwenden Sie sie? Das DI-Muster sprudelt die Abhängigkeitskette hoch. Angenommen, Sie haben eine Komponente MyApp, die verwendet MyList. Der "Singleton" -Ansatz wäre:

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

Der DI-Ansatz lautet:

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

  return MyApp
}

Jetzt können wir testen, MyAppohne MyListdirekt zu testen . Wir könnten sogar MyAppmit einer völlig anderen Art von Liste wiederverwenden . Dieses Muster sprudelt bis zur Wurzel der Komposition . Hier rufen Sie Ihre Fabriken an und verdrahten alle Komponenten.

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'))

Jetzt verwendet unser System eine einzelne Instanz von RESTClient, aber wir haben es so konzipiert, dass Komponenten lose gekoppelt und einfach zu testen sind.

Samuel
quelle
Wenn es Ihnen nichts ausmacht, möchte ich auf Ihre Antwort von mir verweisen, weil ich denke, dass sie die Punkte, die ich nur oberflächlich erwähnt habe, sehr gut illustriert.
Laiv
2
@Laiv, ich denke, der Begriff "DI des armen Mannes" fällt aus der Mode, zugunsten von "reinem DI". Obwohl nicht beabsichtigt, klingt das erstere zu negativ, da es impliziert, dass IoC-Container "reicher" sind.
David Arno
Diese. Ich dachte, Dependency Injection sei extrem kompliziert, als ich es zum ersten Mal sah, aber nach kurzer Zeit ist es extrem leicht zu verstehen. Und @Samuel hat es gut erklärt!
Rhys Johns
1
@Samuel Um DI in React richtig anzuwenden, müsste ich für jede Komponente Fabriken erstellen? MyAppFactorySollte die MyAppKlasse auch in Ihrem Beispiel nicht zurückgegeben werden ?
Mateo Hrastnik
@MattHammond Einfache Komponenten, die keine Abhängigkeiten aufweisen, benötigen keine Fabriken. Und genau so habe ich es gemacht. Es gibt vielleicht andere Ansätze für DI in React, die besser sind, aber ich habe hier die Einfachheit angestrebt. Danke für die Korrektur; Ich habe es jetzt behoben.
Samuel
5

Gemäß Ihren Prämissen (Lernen) lautet die einfachste Antwort Nein. Singletons sind nicht die beste Alternative zur Abhängigkeitsinjektion .

Wenn Lernen das Ziel ist, werden Sie feststellen, dass DI eine wertvollere Ressource in Ihrer Toolbox ist als Singleton. Es mag komplex erscheinen, aber die Lernkurve basiert fast auf allem, was Sie von Grund auf lernen müssen. Legen Sie sich nicht auf Ihre Komfortzone, sonst wird überhaupt nicht gelernt.

Technisch gesehen gibt es kaum einen Unterschied zwischen Singleton und Einzelinstanz (was ich denke, dass Sie versuchen zu tun). Wenn Sie DI lernen, werden Sie feststellen, dass Sie einzelne Instanzen im gesamten Code einfügen können. Sie werden feststellen, dass Ihr Code einfacher zu testen und lose gekoppelt ist. ( Weitere Informationen finden Sie in Samuels Antwort. )

Aber hör nicht hier auf. Implementieren Sie denselben Code mit Singleton. Vergleichen Sie dann beide Ansätze.

Wenn Sie beide Implementierungen verstehen und mit ihnen vertraut sind, können Sie wissen, wann sie geeignet sind, und wahrscheinlich sind Sie in der Lage, die Frage selbst zu beantworten.

Während des Trainings schmieden Sie jetzt Ihre Goldenen Hämmer . Wenn Sie also das Erlernen von DI vermeiden möchten, werden Sie wahrscheinlich jedes Mal Singletons implementieren, wenn Sie DI benötigen.

Laiv
quelle
0

Ich stimme den anderen Antworten zu, dass es eine gute Idee ist, zu lernen, was DI ist und wie man es verwendet.

Die Warnung, dass Singletons das Testen zu schwierig machen, wird normalerweise von Personen gemacht, die statisch typisierte Sprachen (C ++, C #, Java usw.) verwenden .
Im Gegensatz dazu ist es in einer dynamischen Sprache (Javascript, PHP, Python, Ruby usw.) normalerweise kaum schwieriger, einen Singleton durch eine testspezifische Implementierung zu ersetzen, als dies bei Verwendung von DI der Fall wäre.

In diesem Fall empfehle ich die Verwendung eines Designs, das sich für Sie und Ihre Mitentwickler natürlicher anfühlt , da dadurch Fehler vermieden werden. Wenn das zu Singletons führt, soll es so sein.

(Aber andererseits: Lernen Sie DI, bevor Sie diese Entscheidung treffen.)

Lutz Prechelt
quelle