Tworzenie łańcuchów metod to praktyka metod obiektowych zwracających sam obiekt w celu wywołania wyniku dla innej metody. Lubię to:
participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save()
Wydaje się, że jest to dobra praktyka, ponieważ tworzy czytelny kod lub „płynny interfejs”. Jednak wydaje mi się, że zamiast tego wydaje mi się, że łamie notację wywoływania obiektów wynikającą z samej orientacji obiektu - wynikowy kod nie reprezentuje wykonywania działań w wyniku poprzedniej metody, co jest ogólnie oczekiwanym sposobem działania kodu obiektowego:
participant.getSchedule('monday').saveTo('monnday.file')
Ta różnica tworzy dwa różne znaczenia dla notacji z kropką „wywoływania wynikowego obiektu”: W kontekście łączenia w łańcuch powyższy przykład byłby odczytywany jako zapisanie obiektu uczestnika , nawet jeśli w rzeczywistości ma on na celu zapisanie harmonogramu obiekt otrzymany przez getSchedule.
Rozumiem, że różnica polega na tym, czy wywoływana metoda powinna coś zwrócić, czy nie (w takim przypadku zwróci ona sam wywołany obiekt w celu utworzenia łańcucha). Ale tych dwóch przypadków nie da się odróżnić od samej notacji, tylko od semantyki wywoływanych metod. Gdy nie używa się łączenia metod w łańcuch, zawsze mogę wiedzieć, że wywołanie metody operuje na czymś związanym z wynikiem poprzedniego wywołania - w przypadku łączenia w łańcuch to założenie zrywa się i muszę semantycznie przetworzyć cały łańcuch, aby zrozumieć, czym właściwie jest obiekt nazywa się naprawdę. Na przykład:
participant.attend(event).setNotifications('silent').getSocialStream('twitter').postStatus('Joining '+event.name).follow(event.getSocialId('twitter'))
Tam ostatnie dwa wywołania metod odnoszą się do wyniku getSocialStream, podczas gdy poprzednie odnoszą się do uczestnika. Może to zła praktyka pisanie łańcuchów, w których zmienia się kontekst (prawda?), Ale nawet wtedy będziesz musiał ciągle sprawdzać, czy łańcuchy kropek, które wyglądają podobnie, są w rzeczywistości trzymane w tym samym kontekście, czy tylko działają .
Wydaje mi się, że podczas gdy tworzenie łańcuchów metod powierzchownie daje czytelny kod, przeładowanie znaczenia notacji z kropką powoduje tylko większe zamieszanie. Ponieważ nie uważam się za guru programowania, zakładam, że to moja wina. Więc: Czego mi brakuje? Czy rozumiem, że metoda łączenia jest w jakiś sposób niewłaściwa? Czy są przypadki, w których łączenie metod jest szczególnie dobre, czy takie, w których jest szczególnie złe?
Uwaga: Rozumiem, że to pytanie można odczytać jako wypowiedź zamaskowaną jako pytanie. Jednak tak nie jest - naprawdę chcę zrozumieć, dlaczego tworzenie łańcuchów jest uważane za dobrą praktykę i gdzie popełniam błąd, myśląc, że łamie nieodłączną notację obiektową.
źródło
.
, która ignorowałaby wszelkie wartości zwracane przez mehtod i zawsze wywoływała łańcuchowe metody używające tego samego obiektu.Odpowiedzi:
Zgadzam się, że jest to subiektywne. Przeważnie unikam łączenia metod, ale ostatnio znalazłem również przypadek, w którym było to właściwe - miałem metodę, która akceptowała około 10 parametrów i potrzebowała więcej, ale przez większość czasu wystarczyło określić mało. Z zastąpieniami bardzo szybko stało się to bardzo uciążliwe. Zamiast tego wybrałem podejście łańcuchowe:
Metoda łączenia metod była opcjonalna, ale ułatwiła pisanie kodu (szczególnie w przypadku technologii IntelliSense). Pamiętaj jednak, że jest to pojedynczy przypadek i nie jest to ogólna praktyka w moim kodzie.
Chodzi o to, że w 99% przypadków prawdopodobnie można to zrobić równie dobrze lub nawet lepiej bez łączenia metod. Ale jest 1%, gdzie jest to najlepsze podejście.
źródło
P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);
. Masz tę zaletę, że możesz ponownie wykorzystać ten obiekt parametru w innych wywołaniach, co jest plusem!PizzaBuilder.AddSauce().AddDough().AddTopping()
więcej odniesieńlist.add(someItem)
, czytam, że „ten kod jest dodawanysomeItem
dolist
obiektu”. więc kiedy czytamPizzaBuilder.AddSauce()
, czytam to naturalnie jako „ten kod dodaje sos doPizzaBuilder
obiektu”. Innymi słowy, widzę Orchestrator (jeden robi dodawanie) jako metodę, w której kodlist.add(someItem)
lubPizzaBuilder.addSauce()
jest osadzony. Ale myślę, że przykład PizzaBuilder jest trochę sztuczny. Twój przykładMyObject.SpecifySomeParameter(asdasd)
działa dobrze dla mnie.Tylko moje 2 centy;
Łańcuch metod utrudnia debugowanie: - Nie możesz umieścić punktu przerwania w zwięzłym punkcie, więc możesz wstrzymać program dokładnie tam, gdzie chcesz - Jeśli jedna z tych metod zgłasza wyjątek, a otrzymasz numer wiersza, nie masz pojęcia która metoda w „łańcuchu” spowodowała problem.
Myślę, że generalnie dobrą praktyką jest zawsze pisanie bardzo krótkich i zwięzłych linijek. Każda linia powinna wykonać tylko jedno wywołanie metody. Wolę więcej linii niż dłuższe linie.
EDYCJA: komentarz wspomina, że łączenie metod i łamanie wierszy są oddzielne. To prawda. Jednak w zależności od debugera może być możliwe umieszczenie punktu przerwania w środku instrukcji lub nie. Nawet jeśli możesz, użycie oddzielnych wierszy ze zmiennymi pośrednimi zapewnia znacznie większą elastyczność i całą gamę wartości, które możesz sprawdzić w oknie Watch, które pomaga w procesie debugowania.
źródło
Osobiście wolę łączyć metody, które działają tylko na oryginalnym obiekcie, np. Ustawiając wiele właściwości lub wywołując metody narzędziowe.
Nie używam go, gdy jedna lub więcej połączonych metod zwróci w moim przykładzie obiekt inny niż foo. Chociaż składniowo możesz łączyć wszystko, o ile używasz prawidłowego interfejsu API dla tego obiektu w łańcuchu, zmiana obiektów IMHO sprawia, że rzeczy są mniej czytelne i może być naprawdę mylące, jeśli interfejsy API dla różnych obiektów mają jakiekolwiek podobieństwa. Jeśli zrobić kilka naprawdę wspólny wywołanie metody na końcu (
.toString()
,.print()
, cokolwiek), które są przedmiotem ostatecznie działając na podstawie? Ktoś przypadkowo odczytujący kod może nie wychwycić, że byłby to niejawnie zwrócony obiekt w łańcuchu, a nie oryginalne odwołanie.Łańcuch różnych obiektów może również prowadzić do nieoczekiwanych błędów zerowych. W moich przykładach, zakładając, że foo jest poprawne, wszystkie wywołania metod są „bezpieczne” (np. Poprawne dla foo). W przykładzie PO:
... nie ma gwarancji (jako zewnętrzny programista patrząc na kod), że getSchedule faktycznie zwróci prawidłowy, niezerowy obiekt harmonogramu. Ponadto debugowanie tego stylu kodu jest często dużo trudniejsze, ponieważ wiele środowisk IDE nie ocenia wywołania metody w czasie debugowania jako obiektu, który można sprawdzić. IMO, za każdym razem, gdy potrzebujesz obiektu do sprawdzenia w celu debugowania, wolę mieć go w jawnej zmiennej.
źródło
Participant
nie posiada ważnejSchedule
wówczasgetSchedule
metoda ma na celu zwrócićMaybe(of Schedule)
rodzaj isaveTo
metoda przeznaczona jest do zaakceptowaniaMaybe
typu.Martin Fowler ma tutaj dobrą dyskusję:
źródło
Moim zdaniem łączenie metod jest trochę nowością. Jasne, wygląda fajnie, ale nie widzę w tym żadnych prawdziwych zalet.
Jak jest:
lepiej niż:
Wyjątkiem może być sytuacja, gdy addObject () zwraca nowy obiekt, w którym to przypadku odblokowany kod może być nieco bardziej uciążliwy, na przykład:
źródło
Jest to niebezpieczne, ponieważ możesz polegać na większej liczbie obiektów niż oczekiwano, tak jak wtedy, gdy twoje wywołanie zwraca instancję innej klasy:
Podam przykład:
FoodStore to obiekt składający się z wielu posiadanych przez Ciebie sklepów spożywczych. foodstore.getLocalStore () zwraca obiekt, który przechowuje informacje o sklepie najbliższym parametrowi. getPriceforProduct (cokolwiek) jest metodą tego obiektu.
Więc kiedy wywołujesz foodStore.getLocalStore (parametry) .getPriceforProduct (cokolwiek)
polegasz nie tylko na FoodStore, jak Ty, ale także na LocalStore.
Jeśli getPriceforProduct (cokolwiek) kiedykolwiek się zmieni, musisz zmienić nie tylko FoodStore, ale także klasę, która wywołała metodę łańcuchową.
Zawsze powinieneś dążyć do luźnego łączenia między klasami.
Biorąc to pod uwagę, osobiście lubię łączyć je w łańcuch podczas programowania Rubiego.
źródło
Wiele osób używa łączenia metod jako formy wygody, zamiast mieć na uwadze jakiekolwiek problemy z czytelnością. Tworzenie łańcuchów metod jest dopuszczalne, jeśli obejmuje wykonanie tej samej czynności na tym samym obiekcie - ale tylko wtedy, gdy faktycznie poprawia czytelność, a nie tylko w celu napisania mniejszej ilości kodu.
Niestety, wielu stosuje metodę łańcuchową, jak na przykładach podanych w pytaniu. Chociaż nadal można je uczynić czytelnymi, niestety powodują one duże sprzężenie między wieloma klasami, więc nie jest to pożądane.
źródło
Wydaje się to subiektywne.
Łączenie metod nie jest czymś, co jest z natury złe lub dobre imo.
Czytelność jest najważniejsza.
(Weź również pod uwagę, że posiadanie dużej liczby połączonych metod spowoduje, że rzeczy będą bardzo delikatne, jeśli coś się zmieni)
źródło
Korzyści z łączenia w łańcuch,
tj. Gdzie lubię go używać
Jedną z zalet łączenia łańcuchowego, o której nie wspomniałem, była możliwość użycia go podczas zmiennej inicjacji lub podczas przekazywania nowego obiektu do metody, nie mając pewności, czy jest to zła praktyka, czy nie.
Wiem, że to wymyślony przykład, ale powiedz, że masz następujące klasy
I powiedz, że nie masz dostępu do klasy bazowej, lub Powiedz, że wartości domyślne są dynamiczne, oparte na czasie itp. Tak, możesz wtedy utworzyć instancję, a następnie zmienić wartości, ale może to stać się uciążliwe, zwłaszcza jeśli tylko zdasz wartości do metody:
Ale czy to nie jest po prostu dużo łatwiejsze do odczytania:
A co z członkiem klasy?
vs
W ten sposób używałem łańcuchów i zazwyczaj moje metody służą tylko do konfiguracji, więc mają tylko 2 linie, ustaw wartość
Return Me
. Dla nas to wyczyściło ogromne linie, bardzo trudne do odczytania i zrozumienia kodu w jedną linię, która brzmi jak zdanie. coś jakVs Coś jak
Szkoda łańcuchów,
tj. Gdy nie lubię go używać
Nie używam tworzenia łańcuchów, gdy jest wiele parametrów do przekazania do procedur, głównie dlatego, że linie są bardzo długie i jak wspomniał OP, może to być mylące, gdy wywołujesz procedury do innych klas, aby przekazać je do jednej z metody łączenia.
Istnieje również obawa, że procedura zwróci nieprawidłowe dane, dlatego do tej pory używałem łączenia łańcuchowego tylko wtedy, gdy zwracałem tę samą wywoływaną instancję. Jak zostało powiedziane, jeśli tworzysz łańcuch między klasami, utrudniasz debugowanie (która zwróciła wartość null?) I może zwiększyć sprzężenie zależności między klasami.
Wniosek
Jak wszystko w życiu i programowaniu, tworzenie łańcuchów nie jest ani dobre, ani złe, jeśli potrafisz uniknąć zła, wtedy łączenie łańcuchowe może być wielką korzyścią.
Staram się przestrzegać tych zasad.
źródło
Łączenie metod może pozwolić na bezpośrednie projektowanie zaawansowanych DSL w Javie. Zasadniczo możesz modelować przynajmniej te typy reguł DSL:
Reguły te można zaimplementować za pomocą tych interfejsów
Dzięki tym prostym regułom można zaimplementować złożone DSL, takie jak SQL, bezpośrednio w Javie, tak jak robi to jOOQ , biblioteka, którą utworzyłem. Zobacz dość złożony przykład SQL wzięty z mojego bloga tutaj:
Innym fajnym przykładem jest jRTF , mały DSL przeznaczony do tworzenia dokumentów RTF bezpośrednio w Javie. Przykład:
źródło
Łańcuch metod może być po prostu nowością w większości przypadków, ale myślę, że ma swoje miejsce. Jeden przykład można znaleźć w użyciu modułu Active Record w CodeIgniter :
To wygląda dużo czyściej (i moim zdaniem ma więcej sensu) niż:
To naprawdę jest subiektywne; Każdy ma swoje zdanie.
źródło
Myślę, że głównym błędem jest myślenie, że jest to podejście zorientowane obiektowo, podczas gdy w rzeczywistości jest to bardziej funkcjonalne podejście do programowania niż cokolwiek innego.
Głównym powodem, dla którego go używam, jest zarówno czytelność, jak i zapobieganie zalewaniu mojego kodu przez zmienne.
Naprawdę nie rozumiem, o czym mówią inni, kiedy mówią, że szkodzi to czytelności. Jest to jedna z najbardziej zwięzłych i spójnych form programowania, z których korzystałem.
Także to:
convertTextToVoice.LoadText ("source.txt"). ConvertToVoice ("destination.wav");
tak bym go zazwyczaj używał. Używanie go do łączenia x liczby parametrów nie jest tym, jak zwykle go używam. Gdybym chciał wpisać x liczbę parametrów w wywołaniu metody, użyłbym składni params :
public void foo (params object [] items)
I rzutuj obiekty na podstawie typu lub po prostu użyj tablicy lub kolekcji typów danych, w zależności od przypadku użycia.
źródło
Zgadzam się, dlatego zmieniłem sposób implementacji płynnego interfejsu w mojej bibliotece.
Przed:
Po:
W implementacji "before" funkcje modyfikowały obiekt i kończyły się na
return this
. Zmieniłem implementację, aby zwrócić nowy obiekt tego samego typu .Moje uzasadnienie tej zmiany :
Wartość zwracana nie miała nic wspólnego z funkcją, służyła wyłącznie do obsługi części łańcuchowej. Zgodnie z OOP powinna to być funkcja void.
Łańcuch metod w bibliotekach systemowych również implementuje to w ten sposób (jak linq lub string):
Oryginalny obiekt pozostaje nienaruszony, dzięki czemu użytkownik API może zdecydować, co z nim zrobić. Pozwala na:
Realizacja kopia może być również stosowany do obiektów budowlanych:
Gdzie
setBackground(color)
funkcja zmienia instancji i nic nie zwraca (jak jej podobno) .Zachowanie funkcji jest bardziej przewidywalne (patrz punkt 1 i 2).
Użycie krótkiej nazwy zmiennej może również zmniejszyć bałagan w kodzie, bez wymuszania interfejsu API w modelu.
Wniosek:
Moim zdaniem płynny interfejs korzystający z
return this
implementacji jest po prostu zły.źródło
return this
zarządzaniu lub w inny sposób. Moim zdaniem uzyskuje płynne api w „nienaturalny” sposób. (Dodano powód 6: aby pokazać niepłynną alternatywę, która nie ma narzutów / dodatkowych funkcji)Całkowicie pominiętym punktem jest to, że metoda łańcuchowa pozwala na DRY . Jest to skuteczny zamiennik „z” (który jest słabo zaimplementowany w niektórych językach).
Ma to znaczenie z tego samego powodu, dla którego DRY zawsze ma znaczenie; jeśli A okaże się błędem, a te operacje trzeba wykonać na B, wystarczy zaktualizować tylko w 1 miejscu, a nie w 3.
Pragmatycznie korzyść w tym przypadku jest niewielka. Mimo to, trochę mniej pisania, trochę bardziej wytrzymały (DRY), wezmę to.
źródło
Generalnie nienawidzę łańcuchów metod, ponieważ uważam, że pogarsza to czytelność. Zwartość jest często mylona z czytelnością, ale nie są to te same terminy. Jeśli robisz wszystko w jednej instrukcji, jest to zwięzłe, ale w większości przypadków jest mniej czytelne (trudniejsze do zrozumienia) niż w przypadku wielu instrukcji. Jak zauważyłeś, chyba że nie możesz zagwarantować, że wartość zwracana przez używane metody jest taka sama, wtedy łączenie metod będzie źródłem nieporozumień.
1.)
vs
2.)
vs
3.)
vs
Jak widać, wygrywasz prawie nic, ponieważ musisz dodać podziały wierszy do pojedynczego oświadczenia, aby było bardziej czytelne, i musisz dodać wcięcie, aby było jasne, że mówisz o różnych obiektach. Cóż, gdybym chciał użyć języka opartego na identyfikacji, zamiast tego nauczyłbym się Pythona, nie wspominając o tym, że większość IDE usunie wcięcia przez automatyczne formatowanie kodu.
Myślę, że jedynym miejscem, w którym ten rodzaj łączenia może być przydatny, jest przesyłanie strumieni w CLI lub łączenie wielu zapytań razem w SQL. Oba mają cenę za wiele wyciągów. Ale jeśli chcesz rozwiązać złożone problemy, skończysz nawet z tymi, którzy płacą cenę i piszą kod w wielu instrukcjach przy użyciu zmiennych lub piszą skrypty bash i procedury składowane lub widoki.
Według interpretacji SUCHE: „Unikaj powtarzania wiedzy (nie powtarzania tekstu)”. i „Mniej wpisuj, nawet nie powtarzaj tekstów”. Pierwsza z nich to, co tak naprawdę oznacza ta zasada, ale druga to powszechne nieporozumienie, ponieważ wiele osób nie może zrozumieć zbyt skomplikowanych bzdur, takich jak „Każda wiedza musi mieć jedną, jednoznaczną, autorytatywna reprezentacja w systemie ”. Drugi to zwartość za wszelką cenę, co w tym scenariuszu psuje, bo pogarsza czytelność. Pierwsza interpretacja jest przerywana przez DDD podczas kopiowania kodu między ograniczonymi kontekstami, ponieważ luźne sprzężenie jest ważniejsze w tym scenariuszu.
źródło
Dobra:
Źli:
Generał:
Dobrym podejściem jest generalne nieużywanie łańcuchów, dopóki nie pojawią się sytuacje lub określone moduły nie będą do niego szczególnie dopasowane.
W niektórych przypadkach łączenie łańcuchów może poważnie zaszkodzić czytelności, zwłaszcza podczas ważenia w punktach 1 i 2.
W przypadku oskarżenia może być nadużywany, na przykład zamiast innego podejścia (na przykład przekazanie tablicy) lub mieszania metod w dziwaczny sposób (parent.setSomething (). GetChild (). SetSomething (). GetParent (). SetSomething ()).
źródło
Oparta na opiniach odpowiedź
Największą wadą tworzenia łańcuchów jest to, że czytelnikowi może być trudno zrozumieć, jak każda metoda wpływa na oryginalny obiekt, jeśli tak, i jaki typ zwraca każda metoda.
Kilka pytań:
Debugowanie w większości języków może być rzeczywiście trudniejsze w przypadku łączenia w łańcuch. Nawet jeśli każdy krok w łańcuchu znajduje się na osobnej linii (co jest sprzeczne z celem tworzenia łańcucha), sprawdzenie wartości zwracanej po każdym kroku może być trudne, szczególnie w przypadku metod niemutujących.
Czas kompilacji może być wolniejszy w zależności od języka i kompilatora, ponieważ rozwiązanie wyrażeń może być znacznie bardziej złożone.
Uważam, że jak ze wszystkim, łańcuchowanie jest dobrym rozwiązaniem, które może być przydatne w pewnym scenariuszu. Należy go używać ostrożnie, rozumiejąc konsekwencje i ograniczając liczbę elementów łańcucha do kilku.
źródło
W językach typowanych (których brak
auto
lub równoważne) to oszczędza implementatorowi od konieczności deklarowania typów wyników pośrednich.W przypadku dłuższych łańcuchów możesz mieć do czynienia z kilkoma różnymi typami pośrednimi, musisz zadeklarować każdy z nich.
Uważam, że to podejście naprawdę rozwinęło się w Javie, gdzie a) wszystkie wywołania funkcji są wywołaniami funkcji składowych oraz b) wymagane są typy jawne. Oczywiście istnieje tu kompromis w kategoriach utraty pewności, ale w niektórych sytuacjach niektórzy uważają, że warto.
źródło