In vielerlei Hinsicht mag ich die Idee von Fluent-Schnittstellen sehr, aber mit all den modernen Funktionen von C # (Initialisierer, Lambdas, benannte Parameter) denke ich: "Lohnt es sich?" Und "Ist dies das richtige Muster für" verwenden?". Könnte mir jemand, wenn nicht eine akzeptierte Praxis, zumindest seine eigene Erfahrung oder Entscheidungsgrundlage für die Verwendung des Fließenden Musters geben?
Fazit:
Einige gute Faustregeln aus den bisherigen Antworten:
- Fließende Interfaces helfen sehr, wenn Sie mehr Aktionen als Setter haben, da Anrufe mehr vom Kontextdurchgang profitieren.
- Fließende Interfaces sollten als eine Schicht über einer API betrachtet werden, nicht als das einzige Mittel zur Verwendung.
- Die modernen Funktionen wie Lambdas, Initialisierer und benannte Parameter können Hand in Hand arbeiten, um eine flüssige Benutzeroberfläche noch benutzerfreundlicher zu gestalten.
Hier ist ein Beispiel dafür, was ich mit den modernen Funktionen meine, die dafür sorgen, dass sie sich weniger gebraucht anfühlen. Nehmen Sie zum Beispiel ein (vielleicht schlechtes Beispiel) Fluent-Interface, mit dem ich einen Mitarbeiter wie folgt erstellen kann:
Employees.CreateNew().WithFirstName("Peter")
.WithLastName("Gibbons")
.WithManager()
.WithFirstName("Bill")
.WithLastName("Lumbergh")
.WithTitle("Manager")
.WithDepartment("Y2K");
Könnte leicht mit Initialisierern geschrieben werden wie:
Employees.Add(new Employee()
{
FirstName = "Peter",
LastName = "Gibbons",
Manager = new Employee()
{
FirstName = "Bill",
LastName = "Lumbergh",
Title = "Manager",
Department = "Y2K"
}
});
In diesem Beispiel hätte ich auch benannte Parameter in den Konstruktoren verwenden können.
Antworten:
Schreibe eine fließend Schnittstelle (ich habe dabbled mit ihm) braucht mehr Mühe, aber es hat einen Pay-off, denn wenn man es richtig macht, die Absicht des resultierenden Benutzercode mehr offensichtlich. Es ist im Wesentlichen eine Form der domänenspezifischen Sprache.
Mit anderen Worten, wenn Ihr Code viel mehr gelesen wird als geschrieben (und welcher Code nicht?), Sollten Sie in Betracht ziehen, eine flüssige Benutzeroberfläche zu erstellen.
Fließende Interfaces handeln mehr vom Kontext und sind so viel mehr als nur Möglichkeiten, Objekte zu konfigurieren. Wie Sie im obigen Link sehen können, habe ich eine flüssige API verwendet, um Folgendes zu erreichen:
objectA.
Erkennbarkeit (wenn Sie dann zu Intellisense gehen, erhalten Sie viele Hinweise. In meinem obigen Fall erhaltenplm.Led.
Sie alle Optionen für die Steuerung der eingebauten LED und erhaltenplm.Network.
Informationen zu den Funktionen, die Sie mit der Netzwerkschnittstelle ausführen können.plm.Network.X10.
Sie erhalten eine Teilmenge von Netzwerkaktionen für X10-Geräte: Dies erhalten Sie nicht mit Konstruktorinitialisierern (es sei denn, Sie möchten für jeden Aktionstyp ein Objekt erstellen, was nicht idiomatisch ist).Eine Sache, die ich normalerweise mache, ist:
Ich verstehe nicht, wie man so etwas auch ohne eine flüssige Benutzeroberfläche machen kann.
Edit 2 : Sie können auch sehr interessante Verbesserungen der Lesbarkeit vornehmen, wie zum Beispiel:
quelle
Scott Hanselman spricht darüber in Episode 260 seines Podcasts Hanselminutes mit Jonathan Carter. Sie erklären, dass eine flüssige Benutzeroberfläche eher einer Benutzeroberfläche auf einer API gleicht. Sie sollten kein flüssiges Interface als einzigen Zugangspunkt bereitstellen, sondern es als eine Art Code-UI über das "reguläre API-Interface" stellen.
Jonathan Carter spricht in seinem Blog auch ein wenig über API-Design .
quelle
Fließende Schnittstellen sind sehr leistungsstarke Funktionen, die Sie im Kontext Ihres Codes bereitstellen können, wenn Sie die "richtige" Argumentation verwenden.
Wenn Sie einfach massive einzeilige Code-Ketten als eine Art Pseudo-Black-Box erstellen möchten, bellen Sie wahrscheinlich den falschen Baum an. Wenn Sie es andererseits verwenden, um der API-Schnittstelle einen Mehrwert zu verleihen, indem Sie Methodenaufrufe verketten und die Lesbarkeit des Codes verbessern, dann lohnt sich der Aufwand meiner Meinung nach mit viel Planung und Aufwand.
Ich würde vermeiden, dem zu folgen, was bei der Erstellung von fließenden Schnittstellen zu einem gängigen "Muster" zu werden scheint, bei dem Sie alle Ihre fließenden Methoden mit "-etwas" benennen, da es einer potenziell guten API-Schnittstelle den Kontext und damit den inneren Wert nimmt .
Der Schlüssel ist, sich die flüssige Syntax als eine spezifische Implementierung einer domänenspezifischen Sprache vorzustellen. Als ein wirklich gutes Beispiel für das, wovon ich spreche, werfen Sie einen Blick auf StoryQ, mit dessen Hilfe sich fließend eine DSL auf sehr wertvolle und flexible Weise ausdrücken lässt.
quelle
position.withX(5)
gegenposition.getDistanceToOrigin()
Vorbemerkung: Ich gehe von einer Annahme in der Frage aus und ziehe daraus (am Ende dieses Beitrags) meine spezifischen Schlussfolgerungen. Da dies wahrscheinlich keine vollständige und umfassende Antwort ergibt, bezeichne ich dies als CW.
In meinen Augen sollten diese beiden Versionen verschiedene Dinge bedeuten und tun.
Im Gegensatz zur nicht fließenden Version verbirgt die fließende Version die Tatsache, dass das Neue
Employee
ebenfallsAdd
in die Sammlung aufgenommen wirdEmployees
- dies deutet lediglich darauf hin, dass ein neues ObjektCreate
d ist.Die Bedeutung von
….WithX(…)
ist mehrdeutig, insbesondere für Personen, die aus F # kommen, das einwith
Schlüsselwort für Objektausdrücke hat : Sie könnenobj.WithX(x)
als ein neues Objekt interpretiert werden, vonobj
demobj
abgesehen von seinerX
Eigenschaft, deren Wert gleich istx
. Andererseits ist bei der zweiten Version klar, dass keine abgeleiteten Objekte erstellt werden und dass alle Eigenschaften für das ursprüngliche Objekt angegeben sind.Dies
….With…
hat noch eine andere Bedeutung: das Umschalten des "Fokus" der Eigenschaftsinitialisierung auf ein anderes Objekt. Die Tatsache, dass Ihre fließende API zwei unterschiedliche BedeutungenWith
hat, erschwert die korrekte Interpretation des Geschehens. Vielleicht haben Sie in Ihrem Beispiel deshalb Einrückungen verwendet, um die beabsichtigte Bedeutung dieses Codes zu demonstrieren. Es wäre klarer wie folgt:Schlussfolgerungen: Das "Ausblenden" einer ausreichend einfachen Sprachfunktion
new T { X = x }
mit einer fließenden API (Ts.CreateNew().WithX(x)
) ist natürlich möglich, aber:Es muss darauf geachtet werden, dass die Leser des resultierenden fließenden Codes immer noch verstehen, was es genau tut. Das heißt, die flüssige API sollte eine transparente und eindeutige Bedeutung haben. Das Entwerfen einer solchen API ist möglicherweise aufwändiger als erwartet (es muss möglicherweise auf Benutzerfreundlichkeit und Akzeptanz getestet werden) und / oder
Das Entwerfen ist möglicherweise aufwändiger als erforderlich: In diesem Beispiel bietet die fließende API nur einen geringen "Benutzerkomfort" gegenüber der zugrunde liegenden API (einer Sprachfunktion). Man könnte sagen, dass eine flüssige API die zugrunde liegende API / Sprachfunktion "benutzerfreundlicher" machen sollte. das heißt, es sollte dem Programmierer einen beträchtlichen Aufwand ersparen. Wenn es nur eine andere Art ist, dasselbe zu schreiben, lohnt es sich wahrscheinlich nicht, da dies das Leben des Programmierers nicht erleichtert, sondern nur die Arbeit des Designers erschwert (siehe Fazit 1 rechts oben).
In beiden obigen Punkten wird stillschweigend davon ausgegangen, dass die flüssige API eine Schicht über einer vorhandenen API oder einem vorhandenen Sprachfeature ist. Diese Annahme könnte eine weitere gute Richtlinie sein: Eine flüssige API kann eine zusätzliche Möglichkeit sein, etwas zu tun, nicht die einzige. Das heißt, es könnte eine gute Idee sein, eine flüssige API als Option anzubieten.
quelle
Ich mag den fließenden Stil, er drückt Absichten sehr deutlich aus. Mit dem Objektinitalisierer-Beispiel, das Sie nachher haben, müssen Sie Setter für öffentliche Eigenschaften haben, um diese Syntax zu verwenden, und nicht mit dem fließenden Stil. Wenn Sie das sagen, mit Ihrem Beispiel gewinnen Sie nicht viel gegenüber den öffentlichen Setzern, weil Sie sich fast für eine java-artige Methode entschieden haben.
Was mich zum zweiten Punkt bringt, ich bin mir nicht sicher, ob ich den fließenden Stil so verwenden würde, wie Sie es getan haben, mit vielen Eigenschaftssetzern. Ich würde wahrscheinlich die zweite Version dafür verwenden. Ich finde es besser, wenn Sie Sie müssen viele Verben miteinander verketten oder zumindest viele Dinge tun, anstatt Einstellungen vorzunehmen.
quelle
Ich kannte den Begriff der fließenden Benutzeroberfläche nicht , aber er erinnert mich an einige APIs, die ich verwendet habe, einschließlich LINQ .
Persönlich sehe ich nicht, wie moderne Funktionen von C # den Nutzen eines solchen Ansatzes verhindern würden. Ich würde eher sagen, dass sie Hand in Hand gehen. ZB ist es noch einfacher, eine solche Schnittstelle mit Hilfe von Erweiterungsmethoden zu erreichen .
Vielleicht verdeutlichen Sie Ihre Antwort mit einem konkreten Beispiel, wie eine flüssige Benutzeroberfläche durch eine der von Ihnen erwähnten modernen Funktionen ersetzt werden kann.
quelle