Pod wieloma względami bardzo podoba mi się pomysł płynnych interfejsów, ale ze wszystkimi nowoczesnymi funkcjami C # (inicjalizatory, lambdy, nazwane parametry) zastanawiam się, czy warto? posługiwać się?". Czy ktoś mógłby dać mi, jeśli nie zaakceptowaną praktykę, przynajmniej własne doświadczenie lub matrycę decyzyjną na temat tego, kiedy zastosować wzór Płynny?
Wniosek:
Kilka dobrych zasad z dotychczasowych odpowiedzi:
- Płynne interfejsy znacznie pomagają, gdy masz więcej akcji niż seterów, ponieważ połączenia korzystają bardziej z przekazywania kontekstu.
- Płynne interfejsy należy uważać za warstwę nad interfejsem API, a nie za jedyny sposób użycia.
- Nowoczesne funkcje, takie jak lambda, inicjalizatory i nazwane parametry, mogą współpracować ze sobą, dzięki czemu płynny interfejs jest jeszcze bardziej przyjazny.
Oto przykład tego, co rozumiem przez nowoczesne funkcje, które sprawiają, że wydaje się mniej potrzebny. Weźmy na przykład (być może kiepski przykład) Płynny interfejs, który pozwala mi stworzyć pracownika, takiego jak:
Employees.CreateNew().WithFirstName("Peter")
.WithLastName("Gibbons")
.WithManager()
.WithFirstName("Bill")
.WithLastName("Lumbergh")
.WithTitle("Manager")
.WithDepartment("Y2K");
Można go łatwo napisać za pomocą inicjatorów, takich jak:
Employees.Add(new Employee()
{
FirstName = "Peter",
LastName = "Gibbons",
Manager = new Employee()
{
FirstName = "Bill",
LastName = "Lumbergh",
Title = "Manager",
Department = "Y2K"
}
});
W tym przykładzie mogłem również użyć nazwanych parametrów w konstruktorach.
Odpowiedzi:
Napisanie płynnego interfejsu (którym się nim zajmowałem ) wymaga więcej wysiłku, ale przynosi spłatę, ponieważ jeśli zrobisz to dobrze, intencja wynikowego kodu użytkownika jest bardziej oczywista. Zasadniczo jest to forma języka specyficznego dla domeny.
Innymi słowy, jeśli Twój kod jest odczytywany znacznie więcej niż jest napisany (a jakiego kodu nie ma?), Powinieneś rozważyć stworzenie płynnego interfejsu.
Płynne interfejsy dotyczą bardziej kontekstu i są czymś więcej niż tylko sposobem konfigurowania obiektów. Jak widać w powyższym linku, użyłem płynnego interfejsu API, aby osiągnąć:
objectA.
Intellisense daje ci wiele wskazówek. W moim przypadku powyżejplm.Led.
daje ci wszystkie opcje sterowania wbudowaną diodą LED iplm.Network.
daje ci to, co możesz zrobić z interfejsem sieci.plm.Network.X10.
Daje podzbiór akcje sieciowe dla urządzeń X10. Nie dostaniesz tego przy inicjalizatorach konstruktora (chyba że chcesz zbudować obiekt dla każdego rodzaju akcji, co nie jest idiomatyczne).Jedną rzeczą, którą zazwyczaj robię, jest:
Nie rozumiem, jak możesz zrobić coś takiego bez płynnego interfejsu.
Edycja 2 : Możesz także wprowadzić naprawdę interesujące ulepszenia czytelności, takie jak:
źródło
Scott Hanselman mówi o tym w odcinku 260 swojego podcastu Hanselminutes z Jonathanem Carterem. Wyjaśniają, że płynny interfejs bardziej przypomina interfejs użytkownika w interfejsie API. Nie powinieneś zapewniać płynnego interfejsu jako jedynego punktu dostępu, ale raczej dostarczać go jako swego rodzaju interfejsu użytkownika na górze „zwykłego interfejsu API”.
Jonathan Carter mówi także trochę o projektowaniu API na swoim blogu .
źródło
Płynne interfejsy to bardzo potężne funkcje, które można udostępniać w kontekście kodu, gdy podano „właściwe” rozumowanie.
Jeśli Twoim celem jest po prostu utworzenie masywnych jednowierszowych łańcuchów kodu jako rodzaju pseudo-czarnej skrzynki, prawdopodobnie prawdopodobnie szczekasz na niewłaściwe drzewo. Z drugiej strony, jeśli używasz go do dodawania wartości do interfejsu API, zapewniając metodę łączenia wywołań metod i poprawiając czytelność kodu, to przy dobrym planowaniu i wysiłku uważam, że wysiłek jest tego wart.
Unikałbym podążania za czymś, co wydaje się być powszechnym „wzorcem” podczas tworzenia płynnych interfejsów, w których wszystkie swoje płynne metody nazywamy „za pomocą” -sośc, ponieważ okrada to potencjalnie dobry interfejs API jego kontekstu, a zatem i jego wewnętrznej wartości .
Kluczem jest pomyślenie o płynnej składni jako specyficznej implementacji języka specyficznego dla Domeny. Jako naprawdę dobry przykład tego, o czym mówię, spójrz na StoryQ, który wykorzystuje płynność jako środek do wyrażania DSL w bardzo cenny i elastyczny sposób.
źródło
position.withX(5)
versusposition.getDistanceToOrigin()
Uwaga wstępna: Kwestionuję jedno założenie w pytaniu i wyciągam z tego moje konkretne wnioski (na końcu tego postu). Ponieważ prawdopodobnie nie jest to pełna, obejmująca odpowiedź, zaznaczam to jako CW.
Moim zdaniem te dwie wersje powinny oznaczać i robić różne rzeczy.
W przeciwieństwie do wersji niepłynnej, wersja płynna ukrywa fakt, że nowy
Employee
jest równieżAdd
edytowany w kolekcjiEmployees
- sugeruje tylko, że nowym obiektem jestCreate
d.Znaczenie
….WithX(…)
jest niejednoznaczne, szczególnie dla osób pochodzących z F #, które mawith
słowo kluczowe do wyrażeń obiektowych : mogą one interpretowaćobj.WithX(x)
jako nowy obiekt pochodzący zobj
tego, który jest identyczny zobj
wyjątkiem jegoX
właściwości, której wartość jestx
. Z drugiej strony w drugiej wersji jest jasne, że nie są tworzone żadne obiekty pochodne i że wszystkie właściwości są określone dla obiektu oryginalnego.Ma
….With…
to jeszcze inne znaczenie: przełączenie „fokusa” inicjalizacji właściwości na inny obiekt. Fakt, że Twój płynny interfejs API ma dwa różne znaczeniaWith
, utrudnia prawidłową interpretację tego, co się dzieje… dlatego być może użyłeś wcięcia w swoim przykładzie, aby zademonstrować zamierzone znaczenie tego kodu. Byłoby to tak wyraźniejsze:Wnioski: „Ukrywanie” funkcji języka prostego
new T { X = x }
z płynnym API (Ts.CreateNew().WithX(x)
) można oczywiście zrobić, ale:Należy uważać, aby czytelnicy wynikowego płynnego kodu nadal rozumieli, co dokładnie robi. Oznacza to, że płynny interfejs API powinien być przejrzysty i jednoznaczny. Zaprojektowanie takiego interfejsu API może wymagać więcej pracy niż się spodziewano (może wymagać przetestowania pod kątem łatwości użycia i akceptacji) i / lub…
zaprojektowanie go może wymagać więcej pracy niż jest to konieczne: w tym przykładzie płynny interfejs API zapewnia bardzo niewielki „komfort użytkownika” w stosunku do bazowego interfejsu API (funkcja języka). Można powiedzieć, że płynny interfejs API powinien sprawić, że podstawowa funkcja interfejsu API / języka będzie „łatwiejsza w użyciu”; to znaczy, że programista powinien zaoszczędzić sporo wysiłku. Jeśli jest to po prostu inny sposób pisania tego samego, prawdopodobnie nie jest tego wart, ponieważ nie ułatwia życia programistom, a jedynie utrudnia pracę projektanta (patrz konkluzja nr 1 powyżej).
Oba powyższe punkty cicho zakładają, że płynny interfejs API stanowi warstwę w stosunku do istniejącej funkcji interfejsu API lub języka. To założenie może być kolejną dobrą wytyczną: płynne API może być dodatkowym sposobem robienia czegoś, a nie jedynym. Oznacza to, że dobrym pomysłem może być płynne API jako opcja „opt-in”.
źródło
Lubię płynny styl, bardzo wyraźnie wyraża intencję. W przykładzie inicjalizatora obiektów, którego używasz, musisz mieć ustawiające właściwości publiczne, aby używać tej składni, a nie płynny styl. Mówiąc to, na twoim przykładzie nie zyskujesz wiele na publicznych setterach, ponieważ prawie wybrałeś metodę / styl java-esque.
Co prowadzi mnie do drugiego punktu, nie jestem pewien, czy użyłbym płynnego stylu w taki sposób, w jaki masz, z wieloma ustawieniami właściwości, prawdopodobnie użyłbym do tego drugiej wersji, uważam, że lepiej, gdy mieć wiele czasowników do połączenia, lub przynajmniej dużo działań zamiast ustawień.
źródło
Nie znałem terminu płynny interfejs , ale przypomina mi kilka interfejsów API, z których korzystałem, w tym LINQ .
Osobiście nie rozumiem, w jaki sposób nowoczesne funkcje C # uniemożliwiłyby użyteczność takiego podejścia. Wolałbym powiedzieć, że idą w parze. Np. Jeszcze łatwiej jest osiągnąć taki interfejs za pomocą metod rozszerzenia .
Być może wyjaśnij swoją odpowiedź konkretnym przykładem tego, jak płynny interfejs można zastąpić, używając jednej z wymienionych przez ciebie nowoczesnych funkcji.
źródło