Ich habe kürzlich ein Buch mit dem Titel Functional Programming in C # gelesen, und mir fällt auf, dass die unveränderliche und zustandslose Natur der funktionalen Programmierung ähnliche Ergebnisse wie die Abhängigkeitsinjektionsmuster erzielt und möglicherweise sogar ein besserer Ansatz ist, insbesondere im Hinblick auf Komponententests.
Ich wäre dankbar, wenn jeder, der Erfahrung mit beiden Ansätzen hat, seine Gedanken und Erfahrungen austauschen könnte, um die Hauptfrage zu beantworten: Ist funktionale Programmierung eine gangbare Alternative zu Abhängigkeitsinjektionsmustern?
Antworten:
Das Abhängigkeitsmanagement ist aus den folgenden zwei Gründen ein großes Problem in OOP:
Die meisten OO-Programmierer halten die enge Kopplung von Daten und Code für absolut vorteilhaft, doch dies ist mit Kosten verbunden. Das Verwalten des Datenflusses durch die Ebenen ist ein unvermeidlicher Bestandteil der Programmierung in jedem Paradigma. Das Koppeln Ihrer Daten und Ihres Codes fügt das zusätzliche Problem hinzu, dass Sie einen Weg finden müssen, um das Objekt an diesen Punkt zu bringen , wenn Sie eine Funktion an einem bestimmten Punkt verwenden möchten .
Die Verwendung von Nebenwirkungen führt zu ähnlichen Schwierigkeiten. Wenn Sie für einige Funktionen einen Nebeneffekt verwenden, aber die Implementierung austauschen möchten, haben Sie so ziemlich keine andere Wahl, als diese Abhängigkeit einzuschleusen.
Betrachten Sie als Beispiel ein Spammerprogramm, das Webseiten nach E-Mail-Adressen durchsucht und diese dann per E-Mail versendet. Wenn Sie eine DI-Denkweise haben, denken Sie gerade darüber nach, welche Dienste Sie hinter Schnittstellen einkapseln und welche Dienste wo eingebunden werden. Ich lasse dieses Design als Übung für den Leser. Wenn Sie eine FP-Denkweise haben, denken Sie gerade an die Ein- und Ausgänge für die unterste Funktionsebene, wie z.
Wenn Sie in Ein- und Ausgängen denken, gibt es keine Funktionsabhängigkeiten, sondern nur Datenabhängigkeiten. Das macht sie so einfach zum Unit-Test. Ihre nächste Schicht sorgt dafür, dass die Ausgabe einer Funktion in die Eingabe der nächsten eingespeist wird, und kann die verschiedenen Implementierungen bei Bedarf problemlos austauschen.
In einem sehr realen Sinne veranlasst Sie die funktionale Programmierung natürlich dazu, Ihre Funktionsabhängigkeiten immer umzukehren, und deshalb müssen Sie normalerweise keine besonderen Maßnahmen ergreifen, um dies nachträglich zu tun. Wenn Sie dies tun, erleichtern Tools wie Funktionen höherer Ordnung, Verschlüsse und Teilanwendungen das Ausführen mit weniger Boilerplate.
Beachten Sie, dass nicht die Abhängigkeiten selbst problematisch sind. Es sind Abhängigkeiten, die in die falsche Richtung weisen. Die nächste Ebene kann eine Funktion haben wie:
Es ist vollkommen in Ordnung, dass diese Ebene Abhängigkeiten hat, die wie folgt fest codiert sind, da ihr einziger Zweck darin besteht, die Funktionen der unteren Ebene zusammenzukleben. Das Austauschen einer Implementierung ist so einfach wie das Erstellen einer anderen Komposition:
Möglich wird diese leichte Rekomposition durch fehlende Nebenwirkungen. Die Funktionen der unteren Schicht sind völlig unabhängig voneinander. Die nächsthöhere Ebene kann
processText
anhand einiger Benutzerkonfigurationen auswählen, welche tatsächlich verwendet wird:Auch dies ist kein Problem, da alle Abhängigkeiten in eine Richtung weisen. Wir müssen einige Abhängigkeiten nicht invertieren, damit sie alle in die gleiche Richtung weisen, da uns reine Funktionen bereits dazu gezwungen haben.
Beachten Sie, dass Sie dies viel gekoppelter gestalten können, indem
config
Sie zur untersten Ebene durchgehen , anstatt sie oben zu markieren. FP hindert Sie nicht daran, aber es macht Sie viel ärgerlicher, wenn Sie es versuchen.quelle
System.String
. In einem Modulsystem können Sie durchSystem.String
eine Variable ersetzen , sodass die Auswahl der Zeichenfolgenimplementierung nicht fest codiert ist, sondern beim Kompilieren noch aufgelöst wird.Das scheint mir eine merkwürdige Frage zu sein. Ansätze der funktionalen Programmierung sind weitgehend tangential zur Abhängigkeitsinjektion.
Sicher, ein unveränderlicher Zustand kann dazu führen, dass Sie nicht "betrügen", indem Sie Nebenwirkungen haben oder den Klassenzustand als impliziten Vertrag zwischen Funktionen verwenden. Es macht die Weitergabe von Daten expliziter, was meiner Meinung nach die grundlegendste Form der Abhängigkeitsinjektion ist. Und das funktionale Programmierkonzept des Weitergebens von Funktionen erleichtert dies erheblich.
Abhängigkeiten werden jedoch nicht entfernt. Ihre Operationen benötigen immer noch alle Daten / Operationen, die benötigt wurden, als Ihr Zustand veränderlich war. Und irgendwie müssen Sie diese Abhängigkeiten immer noch herbekommen. Ich würde also nicht sagen, dass funktionale Programmieransätze DI überhaupt ersetzen , also keine Alternative.
Wenn überhaupt, haben sie Ihnen gerade gezeigt, wie schlecht OO-Code implizite Abhängigkeiten erzeugen kann, über die Programmierer selten nachdenken.
quelle
Die schnelle Antwort auf Ihre Frage lautet: Nein .
Aber wie andere behauptet haben, verbindet die Frage zwei Begriffe, die in keiner Beziehung zueinander stehen.
Lassen Sie uns dies Schritt für Schritt tun.
DI führt zu einem nicht funktionalen Stil
Der Kern der Funktionsprogrammierung besteht aus reinen Funktionen - Funktionen, die Eingaben auf Ausgaben abbilden, sodass Sie für eine bestimmte Eingabe immer die gleiche Ausgabe erhalten.
DI bedeutet normalerweise, dass Ihr Gerät nicht mehr rein ist, da die Leistung je nach Einspritzung variieren kann. Zum Beispiel in der folgenden Funktion:
getBookedSeatCount
(eine Funktion) kann variieren und zu unterschiedlichen Ergebnissen für die gleiche Eingabe führen. Dies macht auchbookSeats
unrein.Es gibt Ausnahmen dafür - Sie können einen von zwei Sortieralgorithmen einfügen, die dasselbe Eingabe-Ausgabe-Mapping implementieren, obwohl sie unterschiedliche Algorithmen verwenden. Aber das sind Ausnahmen.
Ein System kann nicht rein sein
Die Tatsache, dass ein System nicht rein sein kann, wird ebenso ignoriert, wie es in funktionalen Programmierquellen behauptet wird.
Ein System muss Nebenwirkungen haben. Die offensichtlichen Beispiele sind:
Ein Teil Ihres Systems muss also Nebenwirkungen beinhalten, und dieser Teil kann durchaus auch einen imperativen Stil oder einen OO-Stil beinhalten.
Das Shell-Core-Paradigma
Eine gute System- (oder Modul-) Architektur, die die Begriffe aus Gary Bernhardts hervorragendem Vortrag über Grenzen übernimmt, umfasst die folgenden zwei Ebenen:
Der Schlüssel zum Erfolg besteht darin, das System in einen reinen Teil (den Kern) und einen unreinen Teil (die Hülle) zu unterteilen.
Obwohl dieser Artikel eine etwas fehlerhafte Lösung (und Schlussfolgerung) bietet, schlägt er das gleiche Konzept vor. Die Haskell-Implementierung ist besonders aufschlussreich, da sie zeigt, dass alles mit FP durchgeführt werden kann.
DI und FP
Die Verwendung von DI ist durchaus sinnvoll, auch wenn der Großteil Ihrer Anwendung rein ist. Der Schlüssel besteht darin, den DI in der unreinen Hülle zu begrenzen.
Ein Beispiel sind API-Stubs - Sie möchten die echte API in der Produktion, verwenden jedoch Stubs beim Testen. Das Festhalten am Shell-Core-Modell wird hier sehr hilfreich sein.
Fazit
FP und DI sind also keine Alternativen. Sie haben wahrscheinlich beides in Ihrem System, und es wird empfohlen, die Trennung zwischen dem reinen und dem unreinen Teil des Systems sicherzustellen, in dem sich FP und DI befinden.
quelle
Aus der Sicht von OOP können Funktionen als Einzelmethodenschnittstellen betrachtet werden.
Schnittstelle ist ein stärkerer Vertrag als eine Funktion.
Wenn Sie einen funktionalen Ansatz verwenden und viel DI ausführen, erhalten Sie im Vergleich zu einem OOP-Ansatz mehr Kandidaten für jede Abhängigkeit.
vs
quelle