Czy złą praktyką jest zmuszanie setera do zwrócenia „tego”?


249

Czy to dobry, czy zły pomysł, aby setery w javie zwracały „to”?

public Employee setName(String name){
   this.name = name;
   return this;
}

Ten wzorzec może być przydatny, ponieważ wtedy możesz ustawić łańcuchy ustawiające w następujący sposób:

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!"));

zamiast tego:

Employee e = new Employee();
e.setName("Jack Sparrow");
...and so on...
list.add(e);

... ale to w pewien sposób jest sprzeczne ze standardową konwencją Podejrzewam, że to może być opłacalne tylko dlatego, że może sprawić, że Seter zrobi coś innego pożytecznego. Widziałem, że ten wzorzec wykorzystywał niektóre miejsca (np. JMock, JPA), ale wydaje się rzadki i ogólnie używany tylko w przypadku bardzo dobrze zdefiniowanych interfejsów API, w których ten wzorzec jest używany wszędzie.

Aktualizacja:

To, co opisałem, jest oczywiście ważne, ale tak naprawdę szukam kilku przemyśleń na temat tego, czy jest to ogólnie akceptowalne i czy są jakieś pułapki lub powiązane najlepsze praktyki. Wiem o wzorcu Konstruktora, ale jest on nieco bardziej zaangażowany niż to, co opisuję - jak opisuje to Josh Bloch, istnieje powiązana statyczna klasa Konstruktora do tworzenia obiektów.


1
Ponieważ jakiś czas temu widziałem ten wzór projektowy, robię to, gdzie to możliwe. Jeśli metoda nie musi jawnie zwracać czegoś, aby wykonać swoją pracę, teraz zwraca this. Czasami nawet zmieniam funkcję, aby zamiast zwracać wartość, działa ona na elemencie obiektu, tylko po to, aby to zrobić. To jest wspaniałe. :)
Inversus

4
Dla seterów teleskopowych zwracających siebie oraz w budowniczych wolę używać withName (nazwa ciągu) zamiast setName (nazwa ciągu). Jak wskazałeś, powszechną praktyką i oczekiwaniem rozgrywającego jest powrót do pustki.
Setery

Proszę wprowadzić podział wiersza przed każdym wywołaniem :) I skonfiguruj IDE lub uzyskaj poprawny, jeśli to nie przestrzega.
MauganRa

Powszechnie używane frameworki (na przykład Spring i Hibernacja) ściśle (przynajmniej kiedyś) przestrzegały konwencji
ustalania

Odpowiedzi:


83

Nie sądzę, żeby było w tym coś szczególnie złego, to tylko kwestia stylu. Przydaje się, gdy:

  • Musisz ustawić wiele pól jednocześnie (w tym na budowie)
  • wiesz, które pola musisz ustawić w momencie pisania kodu, i
  • istnieje wiele różnych kombinacji dla pól, które chcesz ustawić.

Alternatywami dla tej metody mogą być:

  1. Jeden mega konstruktor (minus: możesz przekazać wiele wartości null lub wartości domyślnych i trudno jest ustalić, która wartość odpowiada czemu)
  2. Kilka przeciążonych konstruktorów (minus: staje się nieporęczny, gdy masz więcej niż kilka)
  3. Metody fabryczne / statyczne (minus: taki sam jak przeciążonych konstruktorów - staje się niewygodny, gdy jest ich więcej)

Jeśli zamierzasz ustawić tylko kilka właściwości naraz, powiedziałbym, że nie warto zwracać „tego”. Z pewnością spada, jeśli później zdecydujesz się zwrócić coś innego, na przykład wskaźnik / komunikat o stanie / sukcesie.


1
cóż, generalnie i tak niczego nie zwracasz od setera, zgodnie z konwencją.
Ken Liu

17
Może nie na początek, ale seter niekoniecznie zachowuje swój pierwotny cel. To, co kiedyś było zmienną, może zmienić się w stan obejmujący kilka zmiennych lub mieć inne skutki uboczne. Niektórzy seterzy mogą zwrócić poprzednią wartość, inni mogą zwrócić wskaźnik awarii, jeśli błąd jest zbyt powszechny dla wyjątku. Rodzi to jednak kolejną interesującą kwestię: co jeśli narzędzie / struktura, której używasz, nie rozpoznaje twoich ustawiaczy, gdy mają zwracane wartości?
Tom Clift,

12
@Tom dobry punkt, zrobienie tego łamie konwencję „Java bean” dla programów pobierających i ustawiających.
Andy White,

2
@TomClift Czy złamanie konwencji „Java Bean” powoduje jakieś problemy? Są biblioteki, które używają konwencji Java Bean, patrząc na typ zwracany lub tylko parametry metody i nazwę metody.
Theo Briscoe,

3
dlatego istnieje wzorzec Konstruktora, ustawiający nie powinni czegoś zwracać, zamiast tego, tworzą konstruktor, jeśli jego potrzeba wygląda lepiej i zajmuje mniej kodu :)
RicardoE 17.04.15

106

To nie jest zła praktyka. To coraz bardziej powszechna praktyka. Większość języków nie wymaga, abyś zajmował się zwróconym obiektem, jeśli nie chcesz, więc nie zmienia on „normalnej” składni użycia settera, ale pozwala na łączenie seterów razem.

Jest to powszechnie nazywane wzorcem konstruktora lub płynnym interfejsem .

Jest to również powszechne w API Java:

String s = new StringBuilder().append("testing ").append(1)
  .append(" 2 ").append(3).toString();

26
Jest często używany w konstruktorach, ale nie powiedziałbym, że „nazywa się to… Wzorem konstruktora”.
Laurence Gonsalves

10
Zabawne jest dla mnie, że niektóre z płynnych interfejsów usprawniają czytanie kodu. Widziałem, że pisanie jest wygodniejsze, ale wydaje mi się, że trudniej jest je czytać. To jedyne prawdziwe nieporozumienie, jakie mam z tym.
Brent pisze kod

30
Jest również znany jako antipattern wraku pociągu. Problem polega na tym, że gdy ślad stosu wyjątku wskaźnika zerowego zawiera taką linię, nie masz pojęcia, które wywołanie zwróciło null. Nie oznacza to, że za wszelką cenę należy unikać tworzenia łańcuchów, ale wystrzegaj się złych bibliotek (szczególnie domowej roboty).
ddimitrov

18
@ddimitrov aslong jak ograniczyć go do powrotu to nigdy nie byłoby problemu (tylko pierwsza invokation mógł rzucać NPE)
Stefan

4
łatwiej jest pisać ORAZ czytać przy założeniu, że wstawiasz przełamania linii i wcięcia tam, gdzie w przeciwnym razie ucierpiałaby czytelność! (ponieważ unika redundantny bałaganu powtarzania kodu jak bla.foo.setBar1(...) ; bla.foo.setBar2(...)kiedy można napisać bla.foo /* newline indented */.setBar1(...) /* underneath previous setter */ .setBar2(...)(nie można używać linebreaks w komentarzu tak podobny do tego :-( ... nadzieję, że rozważa się punkt 10 takich ustawiające lub bardziej złożone połączenia)
Andreas Dietrich

90

Podsumowując:

  • nazywa się to „płynnym interfejsem” lub „łączeniem metod”.
  • nie jest to „standardowa” Java, chociaż w dzisiejszych czasach jest to bardziej widoczne (działa świetnie w jQuery)
  • narusza specyfikację JavaBean, więc zepsuje się z różnymi narzędziami i bibliotekami, szczególnie z konstruktorami JSP i Spring.
  • może to zapobiec niektórym optymalizacjom, które normalnie wykonałaby JVM
  • niektórzy myślą, że to czyści kod, inni uważają, że to „upiorne”

Kilka innych punktów nie wymienionych:

  • Narusza to zasadę, że każda funkcja powinna wykonać jedną (i tylko jedną) rzecz. Możesz w to wierzyć lub nie, ale w Javie uważam, że działa dobrze.

  • IDE nie wygenerują ich dla Ciebie (domyślnie).

  • I w końcu oto punkt danych w świecie rzeczywistym. Mam problemy z użyciem biblioteki zbudowanej w ten sposób. Konstruktor zapytań Hibernacja jest tego przykładem w istniejącej bibliotece. Ponieważ metody zestawu * zapytania zwracają zapytania, nie można powiedzieć, patrząc tylko na podpis, jak z niego korzystać. Na przykład:

    Query setWhatever(String what);
  • Wprowadza niejednoznaczność: czy metoda modyfikuje bieżący obiekt (twój wzorzec), czy może Query jest naprawdę niezmienny (bardzo popularny i cenny wzorzec), a metoda zwraca nowy. Utrudnia to korzystanie z biblioteki, a wielu programistów nie wykorzystuje tej funkcji. Gdyby setery były seterami, łatwiej byłoby z niego korzystać.


5
tak przy okazji, jest „płynny”, a nie „płynny” ... ponieważ pozwala na uporządkowanie szeregu wywołań metod, takich jak język mówiony.
Ken Liu

1
Ostatni punkt o niezmienności jest bardzo ważny. Najprostszym przykładem jest String. Deweloperzy Java oczekują, że używając metod na String otrzymają zupełnie nową instancję, a nie tę samą, ale zmodyfikowaną instancję. W przypadku płynnego interfejsu należałoby wspomnieć w dokumentacji metody, że zwracanym obiektem jest „to” zamiast nowej instancji.
MeTTeO,

7
Chociaż ogólnie się zgadzam, nie zgadzam się, że narusza to zasadę „rób tylko jedną rzecz”. Powrót thisto mało złożona nauka o rakietach. :-)
user949300

dodatkowa uwaga: narusza również zasadę rozdzielania poleceń i zapytań.
Marton_hun

84

Wolę używać do tego metod „z”:

public String getFoo() { return foo; }
public void setFoo(String foo) { this.foo = foo; }
public Employee withFoo(String foo) {
  setFoo(foo);
  return this;
}

A zatem:

list.add(new Employee().withName("Jack Sparrow")
                       .withId(1)
                       .withFoo("bacon!"));

Ostrzeżenie : ta withXskładnia jest powszechnie używana do zapewnienia „ustawiaczy” dla niezmiennych obiektów, więc osoby wywołujące te metody mogą oczekiwać, że utworzą nowe obiekty, a nie zmutują istniejącą instancję. Może bardziej rozsądnym sformułowaniem byłoby coś takiego:

list.add(new Employee().chainsetName("Jack Sparrow")
                       .chainsetId(1)
                       .chainsetFoo("bacon!"));

Dzięki konwencji nazewnictwa chainsetXyz () praktycznie wszyscy powinni być zadowoleni.


18
+1 Za interesującą konwencję. Nie zamierzam przyjąć go w moim własnym kodzie, ponieważ wygląda na to, że teraz musisz mieć pole getA seti A withdla każdej klasy. Jednak wciąż jest to interesujące rozwiązanie. :)
Paul Manta

1
To zależy od tego, jak często dzwonisz do seterów. Przekonałem się, że jeśli te setery są często wywoływane, warto je dodać, ponieważ upraszczają kod wszędzie indziej. YMMV
Qualidafial

2
A jeśli do tego dodaje Project Lombok„s @Getter/@Setteradnotacji ... że byłoby fantastyczne łańcuchowym. Możesz też użyć czegoś takiego jak Kestrelkombinator ( github.com/raganwald/Katy ), którego używają znajomi JQuery i Javascript.
Ehtesh Choudhury

4
@ AlikElzin-kilaka Właściwie właśnie zauważyłem, że niezmienne klasy java.time w Javie 8 używają tego wzorca, np. LocalDate.withMonth, withYear itp.
Qualidafial

4
withprefiks jest inna konwencja. ponieważ @ qualidafial podał przykład. metody z prefiksem withnie powinny zwracać, thisale raczej nowe wystąpienie, takie jak bieżące, ale withta zmiana. Robi się to, gdy chcesz, aby twoje obiekty były niezmienne. Kiedy więc widzę metodę z prefiksem with, zakładam, że otrzymam nowy obiekt, a nie ten sam obiekt.
tempcke

26

Jeśli nie chcesz wracać 'this'z programu ustawiającego, ale nie chcesz używać drugiej opcji, możesz użyć następującej składni, aby ustawić właściwości:

list.add(new Employee()
{{
    setName("Jack Sparrow");
    setId(1);
    setFoo("bacon!");
}});

Nawiasem mówiąc, myślę, że jest nieco czystszy w C #:

list.Add(new Employee() {
    Name = "Jack Sparrow",
    Id = 1,
    Foo = "bacon!"
});

16
inicjalizacja podwójnego nawiasu klamrowego może mieć problemy z równością, ponieważ tworzy anonimową klasę wewnętrzną; patrz c2.com/cgi/wiki?DoubleBraceInitialization
Csaba_H

@Csaba_H Najwyraźniej ten problem jest winą osoby, która spartaczyła equalsmetodę. Istnieją bardzo czyste sposoby radzenia sobie z anonimowymi zajęciami, equalsjeśli wiesz, co robisz.
AJMansfield

tworzysz nową (anonimową) klasę właśnie do tego? za każdym razem?
user85421,

11

Nie tylko łamie konwencję getters / setters, ale także łamie ramy referencyjne metod Java 8. MyClass::setMyValuejest BiConsumer<MyClass,MyValue>i myInstance::setMyValuejest Consumer<MyValue>. Jeśli masz zwrot setera this, to nie jest on już prawidłową instancją Consumer<MyValue>, a raczej a Function<MyValue,MyClass>, i spowoduje, że cokolwiek przy użyciu odwołań do metod do tych seterów (zakładając, że są to nieważne metody) ulegnie awarii.


2
Byłoby wspaniale, gdyby Java miała jakiś sposób na przeciążenie według typu zwrotu, a nie tylko JVM. Możesz łatwo ominąć wiele z tych przełomowych zmian.
Adowrath

1
Zawsze możesz zdefiniować funkcjonalny interfejs, który rozszerza zarówno, jak Consumer<A>i Function<A,B>domyślną implementację void accept(A a) { apply(a); }. Następnie można go łatwo użyć jako jednego z nich i nie złamie żadnego kodu wymagającego określonej formy.
Steve K

Argh! To jest po prostu źle! Nazywa się to kompatybilnością void. Seter, który zwraca wartość, może działać jako konsument. ideone.com/ZIDy2M
Michael

8

Nie znam Java, ale zrobiłem to w C ++. Inni twierdzą, że sprawia, że ​​linie są naprawdę długie i bardzo trudne do odczytania, ale robiłem to tak wiele razy:

list.add(new Employee()
    .setName("Jack Sparrow")
    .setId(1)
    .setFoo("bacon!"));

To jest jeszcze lepsze:

list.add(
    new Employee("Jack Sparrow")
    .Id(1)
    .foo("bacon!"));

przynajmniej tak mi się wydaje. Ale możesz głosować za mną i nazwać mnie okropnym programistą, jeśli chcesz. I nie wiem, czy wolno ci to robić w Javie.


„Jeszcze lepiej” nie nadaje się dobrze do funkcji formatowania kodu źródłowego dostępnej w nowoczesnych IDE. Niestety.
Thorbjørn Ravn Andersen

prawdopodobnie masz rację ... Jedynym automatycznym formatowaniem, którego użyłem, jest automatyczne wcięcie emacsa.
Carson Myers

2
Formaterery kodu źródłowego można wymusić prostym // po każdym wywołaniu metody w łańcuchu. Ulepsza to trochę twój kod, ale nie tak bardzo, że pionowa seria instrukcji jest sformatowana poziomo.
Qualidafial,

@qualidafial Nie potrzebujesz //po każdej metodzie, jeśli skonfigurujesz IDE, aby nie dołączało już zawiniętych linii (np. Eclipse> Właściwości> Java> Styl kodu> Formatyzator> Zawijanie linii> Nigdy nie dołączaj do już zawiniętych linii).
DJDaveMark

6

Ten schemat (zamierzony gra słów), zwany „płynnym interfejsem”, staje się obecnie dość popularny. Jest do przyjęcia, ale tak naprawdę to nie jest moja filiżanka herbaty.


6

Ponieważ nie zwraca nieważności, nie jest już prawidłowym narzędziem do ustawiania właściwości JavaBean. Może to mieć znaczenie, jeśli jesteś jedną z siedmiu osób na świecie korzystających z wizualnych narzędzi „Bean Builder” lub jedną z 17 osób korzystających z elementów JSP-bean-setProperty.


Ma to również znaczenie, jeśli używasz struktur obsługujących komponenty takie jak Spring.
ddimitrov

6

Przynajmniej teoretycznie może uszkodzić mechanizmy optymalizacji JVM poprzez ustawienie fałszywych zależności między wywołaniami.

Ma to być cukier składniowy, ale w rzeczywistości może powodować skutki uboczne w super-inteligentnej maszynie wirtualnej Java 43.

Dlatego głosuję nie, nie używaj go.


10
Ciekawe ... czy mógłbyś rozwinąć to trochę?
Ken Liu

3
Pomyśl tylko, jak procesory superskalarne radzą sobie z równoległym wykonywaniem. Obiekt do wykonania drugiej setmetody zależy od pierwszej setmetody, chociaż jest znany programistom.
Marian

2
Nadal nie podążam. Jeśli ustawisz Foo, a następnie Bar za pomocą dwóch oddzielnych instrukcji, obiekt, dla którego ustawiasz Bar, ma inny stan niż obiekt, dla którego ustawiasz Foo. Zatem kompilator nie mógł również zrównoleglić tych instrukcji. Przynajmniej nie widzę, jak mogłoby to być bez wprowadzenia nieuzasadnionego założenia (ponieważ nie mam o tym pojęcia, nie zaprzeczę, że Java 43 faktycznie robi równoległość w jednym przypadku, ale nie w drugim przypadku i wprowadza nieuzasadnione założenie w jednym przypadku, ale nie w drugim).
masonk

12
Jeśli nie wiesz, przetestuj. -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining Jva7 jdk zdecydowanie inline łączy łańcuchowe metody i wykonuje tę samą liczbę iteracji, których potrzeba, aby oznaczyć seterów pustki jako gorące i również je wstawić. Uważa, że ​​nie doceniasz mocy algorytmów przycinania kodu operacji JVM; jeśli wie, że to zwracasz, pominie kod op-jrs (instrukcja powrotu java) i pozostawi to na stosie.
Ajax

1
Andreas, zgadzam się, ale problemy pojawiają się, gdy warstwa po warstwie jest niewydajnym kodem. W 99% przypadków powinieneś kodować dla zachowania przejrzystości, co często się tutaj mówi. Ale są też chwile, kiedy trzeba być realistycznym i wykorzystać swoje wieloletnie doświadczenie do przedwczesnej optymalizacji w ogólnym znaczeniu architektonicznym.
LegendLength

6

To wcale nie jest zła praktyka. Ale nie jest kompatybilny ze specyfikacją JavaBeans Spec .

I wiele specyfikacji zależy od tych standardowych akcesoriów.

Zawsze możesz sprawić, że będą ze sobą współistnieć.

public class Some {
    public String getValue() { // JavaBeans
        return value;
    }
    public void setValue(final String value) { // JavaBeans
        this.value = value;
    }
    public String value() { // simple
        return getValue();
    }
    public Some value(final String value) { // fluent/chaining
        setValue(value);
        return this;
    }
    private String value;
}

Teraz możemy ich używać razem.

new Some().value("some").getValue();

Oto kolejna wersja dla niezmiennego obiektu.

public class Some {

    public static class Builder {

        public Some build() { return new Some(value); }

        public Builder value(final String value) {
            this.value = value;
            return this;
        }

        private String value;
    }

    private Some(final String value) {
        super();
        this.value = value;
    }

    public String getValue() { return value; }

    public String value() { return getValue();}

    private final String value;
}

Teraz możemy to zrobić.

new Some.Builder().value("value").build().getValue();

2
Moja edycja została odrzucona, ale twój przykład Konstruktora jest nieprawidłowy. Po pierwsze, .value () niczego nie zwraca, a nawet nie ustawia somepola. Po drugie, powinieneś dodać zabezpieczenie i ustawić somena nullbuild (), aby Somebyło naprawdę niezmienne, w przeciwnym razie możesz ponownie wywołać builder.value()tę samą instancję Konstruktora. I wreszcie, tak, masz konstruktora, ale Somenadal masz konstruktor publiczny, co oznacza, że ​​otwarcie nie popierasz korzystania z konstruktora, tzn. Użytkownik nie wie o tym inaczej niż wypróbowanie lub wyszukanie metody ustawiania niestandardowego valuew ogóle.
Adowrath

@Adowrath, jeśli odpowiedź jest niepoprawna, powinieneś napisać własną odpowiedź, a nie próbować edytować kształtu innej osoby
CalvT

1
@JinKwon Great. Dzięki! I przepraszam, jeśli wcześniej wydawałem się niegrzeczny.
Adowrath,

1
@Adowrath Prosimy o wszelkie dodatkowe uwagi dotyczące ulepszeń. Do twojej wiadomości, nie jestem tą, która odrzuciła twoją edycję. :)
Jin Kwon,

1
Wiem wiem. ^^ I dziękuję za nową wersję, która teraz zapewnia prawdziwy, zmienny konstruktor „Immutable-Some”. I jest to bardziej sprytne rozwiązanie niż moje próby edycji, które, w porównaniu, zaśmiecają kod.
Adowrath,

4

Jeśli zastosujesz tę samą konwencję w całej aplikacji, wydaje się to w porządku.

Z drugiej strony, jeśli istniejąca część twojej aplikacji korzysta ze standardowej konwencji, trzymałbym się jej i dodawałbym konstruktorów do bardziej skomplikowanych klas

public class NutritionalFacts {
    private final int sodium;
    private final int fat;
    private final int carbo;

    public int getSodium(){
        return sodium;
    }

    public int getfat(){
        return fat;
    }

    public int getCarbo(){
        return carbo;
    }

    public static class Builder {
        private int sodium;
        private int fat;
        private int carbo;

        public Builder sodium(int s) {
            this.sodium = s;
            return this;
        }

        public Builder fat(int f) {
            this.fat = f;
            return this;
        }

        public Builder carbo(int c) {
            this.carbo = c;
            return this;
        }

        public NutritionalFacts build() {
            return new NutritionalFacts(this);
        }
    }

    private NutritionalFacts(Builder b) {
        this.sodium = b.sodium;
        this.fat = b.fat;
        this.carbo = b.carbo;
    }
}

1
Właśnie to wzorzec Konstruktora został zaprojektowany do naprawy. Nie łamie lambda w Javie 8, nie psuje wybrednych narzędzi JavaBeans i nie powoduje żadnych problemów z optymalizacją w JVM (ponieważ obiekt istnieje tylko podczas tworzenia instancji). Rozwiązuje również problem „zbyt wielu konstruktorów”, który występuje po prostu przez nieużywanie konstruktorów, a także eliminuje zanieczyszczenie sterty z anonimowych klas podwójnego nawiasu klamrowego.
ndm13

Interesujący punkt - zauważam, że jeśli prawie nic w twojej klasie jest niezmienne (np. Wysoce konfigurowalny GUI), prawdopodobnie prawdopodobnie możesz całkowicie uniknąć Konstruktora.
Philip Guin


3

Jestem zwolennikiem seterów, którzy zwracają „to”. Nie obchodzi mnie, czy to nie jest zgodne z fasolą. Według mnie, jeśli wyrażenie / instrukcja „=” jest w porządku, to setery zwracające wartości są w porządku.


2

Kiedyś wolałem takie podejście, ale zdecydowałem się go nie stosować.

Powody:

  • Czytelność. Dzięki temu kod jest bardziej czytelny, ponieważ każdy setFoo () znajduje się w osobnej linii. Zwykle czytasz kod wiele, wiele razy więcej niż raz.
  • Efekt uboczny: setFoo () powinien ustawiać tylko pole foo, nic więcej. Zwrócenie to dodatkowe „CO to było”.

Wzorzec budowniczego, który widziałem, nie używa konwencji setFoo (foo) .setBar (bar), ale więcej foo (foo) .bar (bar). Być może właśnie z tych powodów.

Jest to, jak zawsze, kwestia gustu. Po prostu lubię podejście „najmniej niespodzianek”.


2
Zgadzam się z efektem ubocznym. Setery, które zwracają rzeczy, naruszają ich nazwę. Ustawiasz foo, ale odzyskujesz obiekt? Czy to nowy obiekt, czy też zmieniłem stary?
crunchdog

2

Ten szczególny wzór nazywa się Łączeniem metod. Link do Wikipedii , zawiera więcej wyjaśnień i przykładów tego, jak odbywa się to w różnych językach programowania.

PS: Właśnie pomyślałem o pozostawieniu go tutaj, ponieważ szukałem konkretnej nazwy.


1

Na pierwszy rzut oka: „Upiorne!”.

Po dalszych przemyśleniach

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!"));

jest w rzeczywistości mniej podatny na błędy niż

Employee anEmployee = new Employee();
anEmployee.setName("xxx");
...
list.add(anEmployee);

To bardzo interesujące. Dodawanie pomysłu do torby na narzędzia ...


1
Nie, to wciąż upiorne. Z punktu widzenia konserwacji ten drugi jest lepszy, ponieważ jest łatwiejszy do odczytania. Ponadto automatyczne sprawdzanie kodu, takie jak CheckStyle, wymusza domyślnie 80 znaków w liniach - kod i tak zostałby zawinięty, co pogłębiłoby problem czytelności / utrzymania. I wreszcie - to Java; pisanie wszystkiego w jednym wierszu nie ma żadnej korzyści, jeśli i tak będzie kompilowane do kodu bajtowego.
OMG Kucyki

1
osobiście uważam, że ten pierwszy jest łatwiejszy do odczytania, zwłaszcza jeśli tworzysz kilka obiektów w ten sposób.
Ken Liu

@Ken: Koduje metodę. Napisz jedną kopię w płynnym formacie; kolejna kopia w drugiej. Teraz przekaż te dwie kopie kilku osobom i zapytaj, który z nich jest dla nich łatwiejszy do odczytania. Szybszy odczyt, szybszy kod.
OMG Kucyki

Jak większość narzędzi, może być łatwo nadużywać. JQuery jest zorientowany na tę technikę i dlatego jest podatny na długie łańcuchy wywołań, które, jak stwierdziłem, faktycznie pogarszają czytelność.
staticsan

2
Byłoby w porządku, gdyby przed każdą kropką były wcięcia linii i wcięcia. Wtedy byłby tak czytelny jak druga wersja. Właściwie więcej, ponieważ listna początku nie ma zbędnych .
MauganRa

1

Tak, myślę, że to dobry pomysł.

Gdybym mógł coś dodać, co z tym problemem:

class People
{
    private String name;
    public People setName(String name)
    {
        this.name = name;
        return this;
    }
}

class Friend extends People
{
    private String nickName;
    public Friend setNickName(String nickName)
    {
        this.nickName = nickName;
        return this;
    }
}

To zadziała:

new Friend().setNickName("Bart").setName("Barthelemy");

To nie zostanie zaakceptowane przez Eclipse! :

new Friend().setName("Barthelemy").setNickName("Bart");

Wynika to z faktu, że setName () zwraca People, a nie Friend, i nie ma PeoplesetNickName.

Jak moglibyśmy napisać settery zwracające klasę SELF zamiast nazwy klasy?

Coś takiego byłoby w porządku (jeśli istniałoby słowo kluczowe SELF). Czy to w ogóle istnieje?

class People
{
    private String name;
    public SELF setName(String name)
    {
        this.name = name;
        return this;
    }
}

1
Istnieje kilka innych kompilatorów Java oprócz Eclipse, które tego nie zaakceptują :). Zasadniczo, używasz drobiu systemu Java (który jest statyczny, a nie dynamiczny, jak niektóre języki skryptowe): musisz rzucić to, co pochodzi z setName () na znajomego, zanim będziesz mógł ustawić na nim SetNickName (). Niestety w przypadku hierarchii dziedziczenia eliminuje to wiele korzyści związanych z czytelnością i sprawia, że ​​ta potencjalnie przydatna technika nie jest tak przydatna.
Cornel Masson

5
Użyj ogólnych. class Chainable <Self extends Chainable> {public Self doSomething () {return (Self) this;}} To nie jest technicznie bezpieczne (rzuci klasę, jeśli zaimplementujesz klasę z typem Self, do którego nie możesz mieć wątpliwości), ale ma poprawną gramatykę, a podklasy zwrócą swój własny typ.
Ajax

Ponadto: „SAMO”, którego używa Baptiste, nazywa się własnym typem, nieobecnym w wielu językach (Scala jest naprawdę jedynym, o którym mogę teraz myśleć), gdzie jako użycie Ajaxa w Generics jest tak zwane „Ciekawie cykliczny wzorzec szablonu ”, który próbuje poradzić sobie z brakiem własnego typu, ale ma pewne wady: (z class C<S extends C<S>>, co jest bezpieczniejsze niż zwykłyS extends C . a) w przypadku A extends B, i B extends C<B>, i masz a, trzeba tylko wiedzieć, że Zwracane jest B, chyba że A zastępuje każdą metodę. b) Nie możesz oznaczać lokalnego, nie-surowego C<C<C<C<C<...>>>>>.
Adowrath,

1

Zasadniczo jest to dobra praktyka, ale może być konieczne, aby funkcje typu set używały typu Boolean w celu ustalenia, czy operacja zakończyła się powodzeniem, czy nie, to też jest jeden sposób. Ogólnie rzecz biorąc, nie ma dogmatu, aby powiedzieć, że to jest dobre łóżko lub łóżko, to oczywiście pochodzi z sytuacji.


2
Co powiesz na stosowanie wyjątków w celu wskazania błędu? Kody błędów można łatwo zignorować, ponieważ wielu programistów C boleśnie się nauczyło. Wyjątki mogą zwiększać stos aż do punktu, w którym można je obsłużyć.
ddimitrov

Wyjątki są zwykle preferowane, ale kody błędów są również przydatne, gdy nie można użyć wyjątków.
Narek

1
Cześć @Narek, być może mógłbyś opracować, w jakich przypadkach lepiej jest używać kodów błędów w ustawieniach niż wyjątków i dlaczego?
ddimitrov

0

Z oświadczenia

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!"));

widzę dwie rzeczy

1) Stwierdzenie bez znaczenia. 2) Brak czytelności.


0

Może to być mniej czytelne

list.add(new Employee().setName("Jack Sparrow").setId(1).setFoo("bacon!")); 

albo to

list.add(new Employee()
          .setName("Jack Sparrow")
          .setId(1)
          .setFoo("bacon!")); 

Jest to o wiele bardziej czytelne niż:

Employee employee = new Employee();
employee.setName("Jack Sparrow")
employee.setId(1)
employee.setFoo("bacon!")); 
list.add(employee); 

8
Myślę, że jest dość czytelny, jeśli nie spróbujesz umieścić całego kodu w jednym wierszu.
Ken Liu,

0

Tworzę moje setery już od dłuższego czasu i jedynym prawdziwym problemem są biblioteki, które trzymają się ścisłego getPropertyDescriptors, aby uzyskać dostęp do czytnika / zapisu komponentu bean. W takich przypadkach java „bean” nie będzie mieć pisarzy, których można by oczekiwać.

Na przykład, nie przetestowałem tego na pewno, ale nie zdziwiłbym się, że Jackson nie rozpoznaje tych ustawników podczas tworzenia obiektów Java z json / map. Mam nadzieję, że się mylę w tej sprawie (wkrótce ją przetestuję).

W rzeczywistości rozwijam lekki ORM zorientowany na SQL i muszę dodać trochę kodu przed getPropertyDescriptors do rozpoznawanych ustawiaczy, które to zwracają.


0

Dawno temu odpowiedz, ale moje dwa centy ... W porządku. Chciałbym, żeby ten płynny interfejs był używany częściej.

Powtórzenie zmiennej „fabrycznej” nie dodaje więcej informacji poniżej:

ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(Foo.class);
factory.setFilter(new MethodFilter() { ...

To jest czystsze, imho:

ProxyFactory factory = new ProxyFactory()
.setSuperclass(Properties.class);
.setFilter(new MethodFilter() { ...

Oczywiście, jako jedna z już wspomnianych odpowiedzi, interfejs API Java musiałby zostać dostosowany, aby zrobić to poprawnie w niektórych sytuacjach, takich jak dziedziczenie i narzędzia.


0

Lepiej jest używać innych konstrukcji języka, jeśli są dostępne. Na przykład w Kotlin korzystałbyś z , aplikował lub pozwalał . Jeśli używasz tego podejścia, tak naprawdę nie będziesz musiał zwracać instancji od settera.

Takie podejście pozwala na kod klienta:

  • Obojętny na typ zwrotu
  • Łatwiejsze w utrzymaniu
  • Unikaj skutków ubocznych kompilatora

Oto kilka przykładów.

val employee = Employee().apply {
   name = "Jack Sparrow"
   id = 1
   foo = "bacon"
}


val employee = Employee()
with(employee) {
   name = "Jack Sparrow"
   id = 1
   foo = "bacon"
}


val employee = Employee()
employee.let {
   it.name = "Jack Sparrow"
   it.id = 1
   it.foo = "bacon"
}

0

Gdy piszę API, używam „return this”, aby ustawić wartości, które zostaną ustawione tylko raz. Jeśli mam inne wartości, które użytkownik powinien móc zmienić, zamiast tego używam standardowego selektora pustek.

Jednak tak naprawdę jest to kwestia preferencji, a moim zdaniem setery łańcuchowe wyglądają całkiem fajnie.


0

Zgadzam się ze wszystkimi plakatami, które twierdzą, że łamie to specyfikację JavaBeans. Są powody, aby to zachować, ale uważam również, że zastosowanie wzorca budowniczego (do którego nawiązywano) ma swoje miejsce; tak długo, jak nie jest wszędzie stosowany, powinno być dopuszczalne. „It's Place” jest dla mnie miejscem, w którym punktem końcowym jest wywołanie metody „build ()”.

Istnieją oczywiście inne sposoby ustawiania tych wszystkich rzeczy, ale zaletą jest to, że unika 1) wieloparametrowych publicznych konstruktorów i 2) częściowo określonych obiektów. Tutaj masz konstruktora, który zbiera to, co jest potrzebne, a następnie wywołuje na końcu jego „build ()”, co może zapewnić, że częściowo określony obiekt nie zostanie skonstruowany, ponieważ ta operacja może mieć mniej niż publiczną widoczność. Alternatywą byłyby „obiekty parametrów”, ale to IMHO popycha problem o jeden poziom wstecz.

Nie lubię konstruktorów wieloparametrowych, ponieważ zwiększają prawdopodobieństwo przekazania wielu argumentów tego samego typu, co może ułatwić przekazanie niewłaściwych argumentów do parametrów. Nie lubię używać wielu ustawień, ponieważ obiekt może być używany, zanim zostanie w pełni skonfigurowany. Ponadto pojęcie posiadania wartości domyślnych w oparciu o poprzednie wybory jest lepiej obsługiwane w metodzie „build ()”.

Krótko mówiąc, uważam, że jest to dobra praktyka, jeśli jest właściwie stosowana.


-4

Zły nawyk: Seter ustaw getter get

co z jawnym zadeklarowaniem metody, która robi to dla U

setPropertyFromParams(array $hashParamList) { ... }

nie nadaje się do autouzupełniania, refaktoryzacji i czytelności anb codeplate
Andreas Dietrich

Ponieważ: 1) Musisz zapamiętać kolejność lub odczytać ją z dokumentów, 2) utracisz wszelkie bezpieczeństwo typu kompilacji, 3) musisz rzucić wartości (to i 2) jest oczywiście bezproblemowe w dynamice język oczywiście), 4) można nie przedłużyć ten pożytkiem innych niż sprawdzanie tablicy długości na każdym wywołaniu, oraz 5) jeśli coś zmienić, czy to zamówienie lub nawet istnienie pewnych wartości, można nie sprawdzać, czy starcie ani w ogóle nie kompiluj czasu bez wątpliwości . Z seterami: 1), 2), 3): Nie ma problemu. 4) Dodanie nowych metod niczego nie psuje. 5) wyraźny komunikat o błędzie.
Adowrath
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.