it-swarm-eu.dev

Wann sollte man C # fließend sprechen?

In vielerlei Hinsicht mag ich die Idee von Fluent-Interfaces wirklich, aber mit all den modernen Funktionen von C # (Initialisierer, Lambdas, benannte Parameter) denke ich: "Lohnt es sich?" Und "Ist dies das richtige Muster dafür?" verwenden?". Könnte mir jemand, wenn nicht eine akzeptierte Praxis, zumindest seine eigene Erfahrung oder Entscheidungsmatrix für die Verwendung des Fluent-Musters geben?

Fazit :

Einige gute Faustregeln aus den bisherigen Antworten:

  • Fließende Schnittstellen sind sehr hilfreich, wenn Sie mehr Aktionen als Setter haben, da Anrufe mehr vom Kontextdurchgang profitieren.
  • Fließende Schnittstellen sollten als Schicht über einer API betrachtet werden, nicht als alleiniges Verwendungszweck.
  • 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 unter den modernen Funktionen verstehe, damit es sich weniger gebraucht anfühlt. Nehmen Sie zum Beispiel eine (vielleicht schlechte) flüssige Oberfläche, mit der ich einen Mitarbeiter erstellen kann, wie:

Employees.CreateNew().WithFirstName("Peter")
                     .WithLastName("Gibbons")
                     .WithManager()
                          .WithFirstName("Bill")
                          .WithLastName("Lumbergh")
                          .WithTitle("Manager")
                          .WithDepartment("Y2K");

Könnte leicht mit Initialisierern wie geschrieben werden:

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.

80
Andrew Hanlon

Das Schreiben einer fließenden Benutzeroberfläche (ich habe versucht damit) erfordert mehr Aufwand, zahlt sich jedoch aus, da die Absicht des resultierenden Benutzercodes offensichtlicher ist, wenn Sie es richtig machen. Es ist im Wesentlichen eine Form der domänenspezifischen Sprache.

Mit anderen Worten, wenn Ihr Code viel mehr gelesen als geschrieben wird (und welcher Code nicht?), Sollten Sie eine flüssige Schnittstelle erstellen.

Bei fließenden Schnittstellen geht es mehr um den Kontext und es geht um viel mehr als nur um die Konfiguration von Objekten. Wie Sie im obigen Link sehen können, habe ich eine fließende API verwendet, um Folgendes zu erreichen:

  1. Kontext (Wenn Sie also normalerweise viele Aktionen in einer Sequenz mit derselben Aktion ausführen, können Sie die Aktionen verketten, ohne Ihren Kontext immer wieder deklarieren zu müssen).
  2. Erkennbarkeit (wenn Sie zu objectA. Gehen, gibt Ihnen Intellisense viele Hinweise. In meinem obigen Fall bietet Ihnen plm.Led. Alle Optionen zur Steuerung der eingebauten LED und plm.Network. gibt Ihnen die Dinge, die Sie mit der Netzwerkschnittstelle tun können. plm.Network.X10. gibt Ihnen die Teilmenge der Netzwerkaktionen für X10-Geräte. Sie erhalten dies nicht mit Konstruktorinitialisierern (es sei denn, Sie müssen ein Objekt für erstellen jede andere Art von Handlung, die nicht idiomatisch ist).
  3. Reflexion (im obigen Beispiel nicht verwendet) - Die Möglichkeit, einen übergebenen LINQ-Ausdruck zu übernehmen und zu bearbeiten, ist ein sehr leistungsfähiges Werkzeug, insbesondere in einigen Hilfs-APIs, die ich für Komponententests erstellt habe. Ich kann einen Property Getter-Ausdruck übergeben, eine ganze Reihe nützlicher Ausdrücke erstellen, diese kompilieren und ausführen oder sogar den Property Getter verwenden, um meinen Kontext einzurichten.

Eine Sache, die ich normalerweise mache, ist:

test.Property(t => t.SomeProperty)
    .InitializedTo(string.Empty)
    .CantBeNull() // tries to set to null and Asserts ArgumentNullException
    .YaddaYadda();

Ich sehe nicht ein, wie man so etwas auch ohne eine fließende Oberfläche machen kann.

Bearbeiten 2 : Sie können auch wirklich interessante Verbesserungen der Lesbarkeit vornehmen, wie zum Beispiel:

test.ListProperty(t => t.MyList)
    .ShouldHave(18).Items()
    .AndThenAfter(t => testAddingItemToList(t))
    .ShouldHave(19).Items();
28
Scott Whitlock

Scott Hanselman spricht darüber in Episode 260 seines Podcasts Hanselminutes mit Jonathan Carter. Sie erklären, dass eine fließende Benutzeroberfläche eher einer Benutzeroberfläche in einer API ähnelt. Sie sollten keine fließende Schnittstelle als einzigen Zugriffspunkt bereitstellen, sondern sie als eine Art Code-Benutzeroberfläche über der "regulären API-Schnittstelle" bereitstellen.

Jonathan Carter spricht auch ein bisschen über API-Design in seinem Blog .

24
Kristof Claes

Fließende Schnittstellen sind sehr leistungsstarke Funktionen, die Sie im Kontext Ihres Codes bereitstellen können, wenn Sie die "richtigen" Argumente verwenden.

Wenn Sie einfach massive einzeilige Codeketten als eine Art Pseudo-Black-Box erstellen möchten, bellen Sie wahrscheinlich den falschen Baum an. Wenn Sie es andererseits verwenden, um Ihrer API-Schnittstelle einen Mehrwert zu verleihen, indem Sie Methoden zum Verketten von Methodenaufrufen und Verbessern der Lesbarkeit von Code bereitstellen, dann lohnt sich der Aufwand meiner Meinung nach mit viel guter Planung und Aufwand.

Ich würde es vermeiden, beim Erstellen fließender Schnittstellen, bei denen Sie alle Ihre fließenden Methoden "mit" -something benennen, einem scheinbar üblichen "Muster" zu folgen, da es einer potenziell guten API-Schnittstelle ihren Kontext und damit ihren inneren Wert nimmt .

Der Schlüssel besteht darin, sich die fließende Syntax als eine spezifische Implementierung einer domänenspezifischen Sprache vorzustellen. Als wirklich gutes Beispiel für das, worüber ich spreche, werfen Sie einen Blick auf StoryQ, das fließend spricht, um ein DSL auf sehr wertvolle und flexible Weise auszudrücken.

14
S.Robins

Erste Anmerkung: Ich habe Probleme mit einer Annahme in der Frage und ziehe meine spezifischen Schlussfolgerungen (am Ende dieses Beitrags). davon. Da dies wahrscheinlich keine vollständige, umfassende Antwort ergibt, markiere ich dies als CW.

Employees.CreateNew().WithFirstName("Peter")…

Könnte leicht mit Initialisierern wie geschrieben werden:

Employees.Add(new Employee() { FirstName = "Peter", … });

In meinen Augen sollten diese beiden Versionen unterschiedliche Bedeutungen haben und tun.

  • Im Gegensatz zur nicht fließenden Version verbirgt die fließende Version die Tatsache, dass das neue Employee auch Add der Sammlung Employees zugeordnet ist - es deutet nur darauf hin, dass ein neues Objekt Created ist .

  • Die Bedeutung von ….WithX(…) ist nicht eindeutig, insbesondere für Personen, die aus F # kommen und ein with - Schlüsselwort für Objektausdrücke haben : Sie können obj.WithX(x) als a interpretieren neu Objekt, das von obj abgeleitet wird und mit obj identisch ist, mit Ausnahme seiner Eigenschaft X, deren Wert x ist. 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.

….WithManager().With…
  • Dieses ….With… Hat noch eine andere Bedeutung: Umschalten des "Fokus" der Eigenschaftsinitialisierung auf ein anderes Objekt. Die Tatsache, dass Ihre fließende API zwei verschiedene Bedeutungen für With hat, macht es schwierig, das Geschehen richtig zu interpretieren. Vielleicht haben Sie in Ihrem Beispiel Einrückungen verwendet, um die beabsichtigte Bedeutung dieses Codes zu demonstrieren. Es wäre klarer so:

    (employee).WithManager(Managers.CreateNew().WithFirstName("Bill").…)
    //                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    //                     value of the `Manager` property appears inside the parentheses,
    //                     like with `WithName`, where the `Name` is also inside the parentheses 
    

Schlussfolgerungen: "Verstecken" einer ausreichend einfachen Sprachfunktion, new T { X = x }, Mit einer fließenden API (Ts.CreateNew().WithX(x)) kann natürlich gemacht werden, aber:

  1. Es muss darauf geachtet werden, dass die Leser des resultierenden fließenden Codes immer noch verstehen, was genau er tut. Das heißt, die fließende API sollte eine transparente Bedeutung haben und eindeutig sein. Das Entwerfen einer solchen API ist möglicherweise aufwändiger als erwartet (möglicherweise muss sie auf Benutzerfreundlichkeit und Akzeptanz getestet werden) und/oder…

  2. das Entwerfen ist möglicherweise aufwändiger als erforderlich: In diesem Beispiel bietet die fließende API der zugrunde liegenden API (einer Sprachfunktion) nur sehr wenig "Benutzerkomfort". Man könnte sagen, dass eine fließende API die zugrunde liegende API/Sprachfunktion "einfacher zu verwenden" machen sollte; Das heißt, es sollte dem Programmierer einen erheblichen Aufwand ersparen. Wenn es nur eine andere Art ist, dasselbe zu schreiben, lohnt es sich wahrscheinlich nicht, weil es das Leben des Programmierers nicht erleichtert, sondern nur die Arbeit des Designers erschwert (siehe Schlussfolgerung Nr. 1 oben).

  3. In beiden obigen Punkten wird stillschweigend davon ausgegangen, dass die fließende API eine Schicht über einer vorhandenen API oder Sprachfunktion ist. Diese Annahme kann eine weitere gute Richtlinie sein: Eine fließende 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 fließende API als "Opt-In" -Option anzubieten.

5
stakx

Ich mag den fließenden Stil, er drückt die Absicht sehr deutlich aus. Mit dem Beispiel für den Objektinitalisierer, das Sie später haben, müssen Sie öffentliche Eigenschaftssetzer haben, um diese Syntax verwenden zu können, nicht mit dem fließenden Stil. Wenn Sie das sagen, gewinnen Sie mit Ihrem Beispiel nicht viel gegenüber den öffentlichen Setzern, weil Sie sich fast für einen Java-ähnlichen Set/Get-Methodenstil 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 würde ich 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.

2
Ian

Ich war mit dem Begriff fließende Schnittstelle nicht vertraut, aber er erinnert mich an einige APIs, die ich verwendet habe, einschließlich LINQ .

Persönlich sehe ich nicht, wie moderne Funktionen von C # die Nützlichkeit eines solchen Ansatzes verhindern würden. Ich würde eher sagen, dass sie Hand in Hand gehen. Z.B. Es ist noch einfacher, eine solche Schnittstelle mit Erweiterungsmethoden zu erreichen.

Klären Sie Ihre Antwort vielleicht anhand eines konkreten Beispiels, wie eine fließende Benutzeroberfläche durch die Verwendung einer der von Ihnen erwähnten modernen Funktionen ersetzt werden kann.

1
Steven Jeuris