Funktionale Programmierung und Abhängigkeitsinversion: Wie kann man den Speicher abstrahieren?

8

Ich versuche eine Lösung mit einer niedrigeren Bibliothek zu erstellen, die wissen muss, dass sie Daten speichern und laden muss, wenn bestimmte Befehle aufgerufen werden. Die Implementierung der Speicher- und Ladefunktionen wird jedoch auf einer Plattform bereitgestellt -spezifisches Projekt, das auf die untergeordnete Bibliothek verweist.

Ich habe einige Modelle wie:

%Vor%

Und was ich tun möchte, ist Funktionen wie:

zu definieren und aufzurufen %Vor%

Gibt es eine Möglichkeit, dies sauber in der funktionalen Sprache zu definieren, vorzugsweise unter Vermeidung eines veränderlichen Zustands (der sowieso nicht notwendig sein sollte)?

Eine Möglichkeit, dies zu tun, könnte etwa so aussehen:

%Vor%

Und es gibt Variationen darüber, aber alle, die ich mir vorstellen kann, beinhalten eine Art nicht initialisierten Zustand. (In Xamarin gibt es auch DependencyService, aber das ist selbst eine Abhängigkeit, die ich vermeiden möchte.)

Gibt es eine Möglichkeit, Code zu schreiben, der eine Speicherfunktion aufruft, die noch nicht implementiert wurde, und sie dann zu implementieren, OHNE einen veränderbaren Zustand zu verwenden?

(Hinweis: Bei dieser Frage geht es nicht um den Speicher selbst - das ist nur das Beispiel, das ich verwende. Es geht darum, Funktionen ohne unnötigen veränderlichen Zustand zu injizieren.)

    
Overlord Zurg 05.09.2015, 09:47
quelle

2 Antworten

15

Andere Antworten hier werden Sie vielleicht darüber aufklären, wie Sie die IO-Monade in F # implementieren, was sicherlich eine Option ist. In F # habe ich oft nur Funktionen mit anderen Funktionen erstellt . Sie müssen dazu keine 'Schnittstelle' oder einen bestimmten Typ definieren.

Entwickeln Sie Ihr System aus dem Outside-In und definieren Sie Ihre übergeordneten Funktionen, indem Sie sich auf das Verhalten konzentrieren, das sie implementieren müssen. Machen Sie sie zu Funktionen höherer Ordnung , indem Sie Abhängigkeiten als Argumente übergeben.

Sie müssen einen Datenspeicher abfragen? Übergeben Sie ein Argument loadUser . Müssen Sie den Benutzer speichern? Übergeben Sie ein Argument saveUser :

%Vor%

Das Argument loadUser wird vom Typ User -> User option und saveUser als User -> unit abgeleitet, weil doSomethingInterestingWith eine Funktion des Typs User -> User ist.

Sie können nun loadUser und saveUser "implementieren", indem Sie Funktionen schreiben, die in die untergeordnete Bibliothek aufrufen.

Die typische Reaktion, die ich auf diesen Ansatz bekomme, ist: Dazu muss ich zu viele Argumente an meine Funktion übergeben!

Wenn das tatsächlich passiert, bedenken Sie, ob das nicht ein Geruch ist, den die Funktion zu sehr versucht.

Da das Dependency Inversion-Prinzip im Titel dieser Frage erwähnt wird, möchte ich darauf hinweisen, dass der SOLID-Prinzipien funktionieren am besten, wenn sie alle gemeinsam angewendet werden. Das Interface Separation Principle besagt, dass die Schnittstellen so klein wie möglich sein sollten und nicht kleiner als bei jedem " Schnittstelle 'ist eine einzelne Funktion.

Für einen ausführlicheren Artikel, der diese Technik beschreibt, können Sie meinen Typ-Driven Development Artikel .

    
Mark Seemann 05.09.2015, 10:43
quelle
1

Sie können den Speicher hinter der Schnittstelle IStorage abstrahieren. Ich denke, das war deine Absicht.

%Vor%

In einem anderen Teil Ihres Programms können Sie mehrere Speicherimplementierungen haben.

%Vor%

Und nachdem Sie alle Ihre Typen definiert haben, können Sie entscheiden, welche Sie verwenden möchten.

%Vor%     
Bartek Kobyłecki 05.09.2015 10:43
quelle