Nazwany idiom parametru w Javie


81

Jak zaimplementować idiom nazwanych parametrów w Javie? (szczególnie dla konstruktorów)

Szukam składni podobnej do Objective-C, a nie takiej używanej w JavaBeans.

Mały przykład kodu byłby w porządku.

Dzięki.

Odpowiedzi:


105

Najlepszym idiomem Java, jaki wydaje mi się do symulacji argumentów słów kluczowych w konstruktorach, jest wzorzec Builder, opisany w Effective Java 2nd Edition .

Podstawowym pomysłem jest posiadanie klasy Builder, która ma metody ustawiające (ale zwykle nie pobierające) dla różnych parametrów konstruktora. Jest też build()metoda. Klasa Builder jest często (statyczną) zagnieżdżoną klasą klasy, z której została zbudowana. Konstruktor klasy zewnętrznej jest często prywatny.

Wynik końcowy wygląda mniej więcej tak:

public class Foo {
  public static class Builder {
    public Foo build() {
      return new Foo(this);
    }

    public Builder setSize(int size) {
      this.size = size;
      return this;
    }

    public Builder setColor(Color color) {
      this.color = color;
      return this;
    }

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

    // you can set defaults for these here
    private int size;
    private Color color;
    private String name;
  }

  public static Builder builder() {
      return new Builder();
  }

  private Foo(Builder builder) {
    size = builder.size;
    color = builder.color;
    name = builder.name;
  }

  private final int size;
  private final Color color;
  private final String name;

  // The rest of Foo goes here...
}

Aby utworzyć instancję Foo, napisz coś takiego:

Foo foo = Foo.builder()
    .setColor(red)
    .setName("Fred")
    .setSize(42)
    .build();

Główne zastrzeżenia to:

  1. Konfigurowanie wzorca jest dość rozwlekłe (jak widać). Prawdopodobnie nie warto, z wyjątkiem zajęć, które planujesz utworzyć w wielu miejscach.
  2. Nie ma sprawdzania w czasie kompilacji, czy wszystkie parametry zostały podane dokładnie raz. Możesz dodać testy uruchomieniowe lub możesz użyć tego tylko dla parametrów opcjonalnych i ustawić wymagane parametry jako normalne parametry dla Foo lub konstruktora Buildera. (Ludzie na ogół nie przejmują się przypadkiem, w którym ten sam parametr jest ustawiany wiele razy).

Możesz również sprawdzić ten wpis na blogu (nie mojego autorstwa).


12
To naprawdę nie jest nazwane parametry w sposób, w jaki robi je Objective-C. Wygląda bardziej na płynny interfejs. To naprawdę nie to samo.
Asaf

30
Lubię używać .withFooraczej niż .setFoo: newBuilder().withSize(1).withName(1).build()zamiastnewBuilder().setSize(1).setName(1).build()
notnoop

17
Asaph: tak, wiem. Java nie ma nazwanych parametrów. Dlatego powiedziałem, że jest to „najlepszy idiom Java, jaki wydaje mi się do symulacji argumentów słów kluczowych”. „Nazwane parametry” Obiektu-C są również mniej niż idealne, ponieważ wymuszają określony porządek. Nie są prawdziwymi argumentami słów kluczowych, jak w Lispie czy Pythonie. Przynajmniej w przypadku wzorca Java Builder wystarczy zapamiętać nazwy, a nie kolejność, tak jak prawdziwe argumenty słów kluczowych.
Laurence Gonsalves

14
notnoop: Wolę „set”, ponieważ są to setery, które modyfikują stan Buildera. Tak, "z" wygląda ładnie w prostym przypadku, gdy łączysz wszystko razem, ale w bardziej skomplikowanych przypadkach, gdy masz Konstruktora we własnej zmiennej (być może dlatego, że warunkowo ustawiasz właściwości) Podoba mi się ten zestaw Prefiks całkowicie wyjaśnia, że ​​Konstruktor jest modyfikowany, gdy wywoływane są te metody. Prefiks „z” brzmi dla mnie funkcjonalnie, a te metody zdecydowanie nie działają.
Laurence Gonsalves

4
There's no compile-time checking that all of the parameters have been specified exactly once.Ten problem można rozwiązać, zwracając interfejsy, Builder1w BuilderNktórych każdy obejmuje jeden z ustawiaczy lub build(). Jest znacznie bardziej szczegółowy w kodzie, ale zawiera obsługę kompilatora dla DSL i sprawia, że ​​autouzupełnianie jest bardzo przyjemne w użyciu.
rsp

73

Warto wspomnieć:

Foo foo = new Foo() {{
    color = red;
    name = "Fred";
    size = 42;
}};

tak zwany inicjator podwójnego nawiasu . W rzeczywistości jest to klasa anonimowa z inicjatorem wystąpienia.


26
Interesująca technika, ale wydaje się trochę droga, ponieważ za każdym razem, gdy użyję jej w swoim kodzie, utworzy nową klasę.
Czerwona hiena

6
Pomijając ostrzeżenia o automatycznym formatowaniu, podklasowaniu i serializacji, jest to w rzeczywistości dość zbliżone do składni C # dla inicjowania opartego na właściwościach. Jednak C # od wersji 4.0 ma również nazwane parametry, więc programiści są naprawdę rozpieszczani wyborem, w przeciwieństwie do programistów Java, którzy muszą symulować idiomy, które uniemożliwiają im później strzelenie sobie w stopę.
Distortum

3
Cieszę się, że jest to możliwe, ale musiałem głosować przeciw, ponieważ to rozwiązanie jest drogie, jak wskazała Czerwona Hiena. Nie mogę się doczekać, aż Java faktycznie obsługuje nazwane parametry, tak jak robi to Python.
Gattster,

12
Głosuj za. To odpowiada na pytanie w najbardziej czytelny, zwięzły sposób. W porządku. To „nie jest wydajne”. O ile dodatkowych milisekund i bitów mówimy tutaj? Któryś z nich? Kilkadziesiąt? Nie zrozum mnie źle - nie będę tego używał, ponieważ nie chciałbym być wykonywany przez rozwlekłego orzecha Java (gra słów zamierzona;)
steve

2
Doskonały! Wymaga tylko pól publicznych / chronionych. To zdecydowanie najlepsze rozwiązanie, które powoduje znacznie mniejszy koszt niż builder. Hyena / Gattster: proszę (1) przeczytać JLS i (2) sprawdzić wygenerowany kod bajtowy przed napisaniem komentarzy.
Jonatan Kaźmierczak

21

Możesz również spróbować skorzystać z porad tutaj: http://www.artima.com/weblogs/viewpost.jsp?thread=118828

int value; int location; boolean overwrite;
doIt(value=13, location=47, overwrite=true);

W witrynie wywołania jest gadatliwy, ale ogólnie daje najniższy narzut.


3
To dobre z powodu niskiego narzutu, ale wydaje się takie hakerskie. Prawdopodobnie użyję metody Builder () w przypadkach, w których jest wiele argumentów.
Gattster,

23
Myślę, że to całkowicie mija się z celem nazwanych parametrów. (co ma coś kojarzyć nazwy z wartościami ). Nic nie wskazuje na to , czy zmienisz kolejność. Zamiast tego radziłbym po prostu dodać komentarz:doIt( /*value*/ 13, /*location*/ 47, /*overwrite*/ true )
Scheintod

20

Styl Java 8:

public class Person {
    String name;
    int age;

    private Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    static PersonWaitingForName create() {
        return name -> age -> new Person(name, age);
    }

    static interface PersonWaitingForName {
        PersonWaitingForAge name(String name);
    }

    static interface PersonWaitingForAge {
        Person age(int age);
    }

    public static void main(String[] args) {

        Person charlotte = Person.create()
            .name("Charlotte")
            .age(25);

    }
}
  • nazwane parametry
  • ustal kolejność argumentów
  • kontrola statyczna -> żadna osoba bezimienna nie jest możliwa
  • trudne do przypadkowego przełączenia argumentów tego samego typu (tak jak jest to możliwe w konstruktorach teleskopów)

3
miły. Jaka szkoda, że ​​nie ma zmiennej kolejności argumentów. (Ale nie mówię, że
użyłbym

1
To genialny pomysł. Definicja create()zatrzymała mnie na tropie. Nigdy nie widziałem takiego stylu łańcucha lambda w Javie. Czy po raz pierwszy odkryłeś ten pomysł w innym języku z lambdami?
kevinarpe

2
Nazywa się currying: en.wikipedia.org/wiki/Currying . Btw: Może to sprytny pomysł, ale nie polecałbym tego stylu nazwanych argumentów. Przetestowałem to w prawdziwym projekcie z wieloma argumentami i prowadzi to do trudnego do odczytania i trudnego w nawigacji kodu.
Alex

Ostatecznie Java otrzyma nazwane parametry w stylu Visual Basic. Java nie robiła tego wcześniej, ponieważ C ++ nie. Ale w końcu tam dotrzemy. Powiedziałbym, że 90% polimorfizmu w Javie to po prostu hakowanie wokół opcjonalnych parametrów.
Tuntable

7

Java nie obsługuje nazwanych parametrów podobnych do celu w C dla konstruktorów lub argumentów metod. Co więcej, tak naprawdę nie jest to sposób działania języka Java. W Javie typowym wzorcem są werbalne nazwy klas i członków. Klasy i zmienne powinny być rzeczownikami, a nazwana metoda - czasownikami. Przypuszczam, że możesz wykazać się kreatywnością i odejść od konwencji nazewnictwa Javy i emulować paradygmat Objective-C w hakerski sposób, ale nie byłoby to szczególnie doceniane przez przeciętnego programistę Java odpowiedzialnego za utrzymanie twojego kodu. Pracując w dowolnym języku, powinieneś trzymać się konwencji języka i społeczności, zwłaszcza podczas pracy w zespole.


4
+1 - za poradę dotyczącą trzymania się idiomów języka, którego aktualnie używasz. Pomyśl proszę o innych ludziach, którzy będą musieli przeczytać Twój kod!
Stephen C

3
Głosowałem za twoją odpowiedzią, ponieważ myślę, że masz rację. Gdybym miał zgadywać, dlaczego dostałeś głos przeciw, to prawdopodobnie dlatego, że to nie odpowiada na pytanie. P: „Jak zrobić nazwane parametry w Javie?” O: „Nie masz”
Gattster,

12
Głosowałem w dół, ponieważ myślę, że twoja odpowiedź nie ma nic wspólnego z pytaniem. Pełne nazwy tak naprawdę nie rozwiązują problemu z kolejnością parametrów. Tak, możesz zakodować je w nazwie, ale jest to oczywiście niewykonalne. Przywołanie niepowiązanego paradygmatu nie wyjaśnia, dlaczego jeden paradygmat nie jest obsługiwany.
Andreas Mueller

7

Jeśli używasz języka Java 6, możesz użyć parametrów zmiennych i zaimportować statyczne, aby uzyskać znacznie lepszy wynik. Szczegóły tego można znaleźć w:

http://zinzel.blogspot.com/2010/07/creating-methods-with-named-parameters.html

Krótko mówiąc, możesz mieć coś takiego:

go();
go(min(0));
go(min(0), max(100));
go(max(100), min(0));
go(prompt("Enter a value"), min(0), max(100));

2
Podoba mi się, ale nadal rozwiązuje tylko połowę problemu. W Javie nie można zapobiec przypadkowej transpozycji parametrów bez utraty sprawdzania wymaganych wartości w czasie kompilacji.
cdunn2001

Bez bezpieczeństwa typów jest to gorsze niż proste // komentarze.
Peter Davis,

7

Chciałbym zaznaczyć, że ten styl odnosi się zarówno do nazwanego parametru, jak i do właściwości bez prefiksu get i set, który ma inny język. To nie jest konwencjonalne w dziedzinie Java, ale jest prostsze, nietrudne do zrozumienia, szczególnie jeśli masz do czynienia z innymi językami.

public class Person {
   String name;
   int age;

   // name property
   // getter
   public String name() { return name; }

   // setter
   public Person name(String val)  { 
    name = val;
    return this;
   }

   // age property
   // getter
   public int age() { return age; }

   // setter
   public Person age(int val) {
     age = val;
     return this;
   }

   public static void main(String[] args) {

      // Addresses named parameter

      Person jacobi = new Person().name("Jacobi").age(3);

      // Addresses property style

      println(jacobi.name());
      println(jacobi.age());

      //...

      jacobi.name("Lemuel Jacobi");
      jacobi.age(4);

      println(jacobi.name());
      println(jacobi.age());
   }
}

6

Co powiesz na

public class Tiger {
String myColor;
int    myLegs;

public Tiger color(String s)
{
    myColor = s;
    return this;
}

public Tiger legs(int i)
{
    myLegs = i;
    return this;
}
}

Tiger t = new Tiger().legs(4).color("striped");

5
Builder jest znacznie lepszy, ponieważ możesz sprawdzić niektóre ograniczenia funkcji build (). Ale wolę też krótsze argumenty bez prefiksu set / with.
rkj

4
Ponadto wzorzec konstruktora jest lepszy, ponieważ pozwala uczynić zbudowaną klasę (w tym przypadku Tiger) niezmienną.
Jeff Olson,

2

Możesz użyć zwykłego konstruktora i metod statycznych, które nadają argumentom nazwę:

public class Something {

    String name;
    int size; 
    float weight;

    public Something(String name, int size, float weight) {
        this.name = name;
        this.size = size;
        this.weight = weight;
    }

    public static String name(String name) { 
        return name; 
    }

    public static int size(int size) {
        return size;
    }

    public float weight(float weight) {
        return weight;
    }

}

Stosowanie:

import static Something.*;

Something s = new Something(name("pen"), size(20), weight(8.2));

Ograniczenia w porównaniu z rzeczywistymi nazwanymi parametrami:

  • kolejność argumentów jest istotna
  • Listy zmiennych argumentów nie są możliwe w przypadku jednego konstruktora
  • potrzebujesz metody dla każdego argumentu
  • nie naprawdę lepsze niż komentarz (nowe coś ( /*name*/ "pen", /*size*/ 20, /*weight*/ 8.2))

Jeśli masz wybór, spójrz na Scala 2.8. http://www.scala-lang.org/node/2075


2
Jedną z wad tego podejścia jest konieczność podania argumentów we właściwej kolejności. Powyższy kod pozwoliłby napisać: Coś s = nowe Coś (nazwa ("długopis"), rozmiar (20), rozmiar (21)); Takie podejście nie pomaga również uniknąć wpisywania opcjonalnych argumentów.
Matt Quail,

1
Głosowałbym za tym do analizy: not really better than a comment... z drugiej strony ...;)
Scheintod

2

Używając lambd Java 8, możesz zbliżyć się jeszcze bardziej do rzeczywistych nazwanych parametrów.

foo($ -> {$.foo = -10; $.bar = "hello"; $.array = new int[]{1, 2, 3, 4};});

Zwróć uwagę, że prawdopodobnie narusza to kilka tuzinów „najlepszych praktyk Java” (jak wszystko, co wykorzystuje ten $symbol).

public class Main {
  public static void main(String[] args) {
    // Usage
    foo($ -> {$.foo = -10; $.bar = "hello"; $.array = new int[]{1, 2, 3, 4};});
    // Compare to roughly "equivalent" python call
    // foo(foo = -10, bar = "hello", array = [1, 2, 3, 4])
  }

  // Your parameter holder
  public static class $foo {
    private $foo() {}

    public int foo = 2;
    public String bar = "test";
    public int[] array = new int[]{};
  }

  // Some boilerplate logic
  public static void foo(Consumer<$foo> c) {
    $foo foo = new $foo();
    c.accept(foo);
    foo_impl(foo);
  }

  // Method with named parameters
  private static void foo_impl($foo par) {
    // Do something with your parameters
    System.out.println("foo: " + par.foo + ", bar: " + par.bar + ", array: " + Arrays.toString(par.array));
  }
}

Plusy:

  • Znacznie krótszy niż jakikolwiek wzór konstruktora, jaki widziałem do tej pory
  • Działa zarówno dla metod, jak i konstruktorów
  • Całkowicie bezpieczne
  • Wygląda bardzo podobnie do rzeczywistych nazwanych parametrów w innych językach programowania
  • Jest mniej więcej tak bezpieczny, jak typowy wzorzec konstruktora (można wielokrotnie ustawiać parametry)

Cons:

  • Twój szef prawdopodobnie cię za to zlinczuje
  • Trudniej powiedzieć, co się dzieje

1
Wady: pola są publiczne i nie są ostateczne. Jeśli nie masz nic przeciwko temu, dlaczego nie użyć po prostu seterów? Jak to działa w przypadku metod?
Alex

Można użyć seterów, ale o co w tym chodzi? Po prostu wydłuża kod, a to wyeliminowałoby korzyści płynące z robienia tego w ten sposób. Przypisanie jest wolne od skutków ubocznych, a ustawiacze to czarne skrzynki. $foonigdy nie ucieka do dzwoniącego (chyba że ktoś przypisze to do zmiennej w wywołaniu zwrotnym), więc dlaczego nie mogą być publiczne?
Vic

2

Możesz użyć adnotacji projektu Lombok @Builder do symulacji nazwanych parametrów w Javie. Spowoduje to wygenerowanie konstruktora, którego możesz użyć do tworzenia nowych instancji dowolnej klasy (zarówno klas, które napisałeś, jak i tych pochodzących z bibliotek zewnętrznych).

Oto jak włączyć to na zajęciach:

@Getter
@Builder
public class User {
    private final Long id;
    private final String name;
}

Następnie możesz użyć tego przez:

User userInstance = User.builder()
    .id(1L)
    .name("joe")
    .build();

Jeśli chcesz stworzyć taki Builder dla klasy pochodzącej z biblioteki, utwórz metodę statyczną z adnotacjami w następujący sposób:

class UserBuilder {
    @Builder(builderMethodName = "builder")
    public static LibraryUser newLibraryUser(Long id, String name) {
        return new LibraryUser(id, name);
    }
  }

Spowoduje to wygenerowanie metody o nazwie „builder”, którą można wywołać:

LibraryUser user = UserBuilder.builder()
    .id(1L)
    .name("joe")
    .build();

Google auto / value służy podobnemu celowi, ale używa struktury przetwarzania adnotacji, która jest znacznie bezpieczniejsza (po aktualizacji JVM wszystko będzie nadal działać) niż manipulacja kodem bajtowym projektu Lombocks.
René

1
Myślę, że auto / wartość Google wymaga trochę dodatkowej mili w porównaniu do korzystania z Lombok. Podejście Lombok jest mniej więcej kompatybilne z tradycyjnym pisaniem JavaBean (np. Możesz tworzyć instancje za pomocą nowego, pola pojawiają się prawidłowo w debugerze itp.). Nie jestem też wielkim fanem manpiluacji kodu bajtowego + wtyczki IDE, z której korzysta Lombok, ale muszę przyznać, że w praktyce działa to OK. Na razie brak problemów ze zmianami wersji JDK, refleksjami itp.
Istvan Devai

Tak, to prawda. W przypadku auto / value musisz podać klasę abstrakcyjną, która zostanie następnie zaimplementowana. Lombok potrzebuje dużo mniej kodu. Dlatego należy porównać zalety i wady.
René,

2

Wydaje mi się, że „obejście problemu z komentarzami” zasługuje na własną odpowiedź (ukrytą w istniejących odpowiedziach i wymienioną w komentarzach tutaj).

someMethod(/* width */ 1024, /* height */ 768);

1

To jest wariant BuilderWzorca, jak opisał Lawrence powyżej.

Często tego używam (w odpowiednich miejscach).

Główna różnica polega na tym, że w tym przypadku Budowniczy jest odporny na szczepienia . Ma to tę zaletę, że można go użyć ponownie i jest bezpieczny dla wątków.

Możesz więc użyć tego do stworzenia jednego domyślnego Konstruktora, a następnie w różnych miejscach, w których go potrzebujesz, możesz go skonfigurować i zbudować obiekt.

Ma to największy sens, jeśli budujesz ten sam obiekt w kółko, ponieważ wtedy możesz ustawić program budujący jako statyczny i nie musisz się martwić o zmianę jego ustawień.

Z drugiej strony, jeśli musisz budować obiekty ze zmieniającymi się parametrami, powoduje to pewne obciążenie. (ale hej, możesz łączyć generowanie statyczne / dynamiczne z niestandardowymi buildmetodami)

Oto przykładowy kod:

public class Car {

    public enum Color { white, red, green, blue, black };

    private final String brand;
    private final String name;
    private final Color color;
    private final int speed;

    private Car( CarBuilder builder ){
        this.brand = builder.brand;
        this.color = builder.color;
        this.speed = builder.speed;
        this.name = builder.name;
    }

    public static CarBuilder with() {
        return DEFAULT;
    }

    private static final CarBuilder DEFAULT = new CarBuilder(
            null, null, Color.white, 130
    );

    public static class CarBuilder {

        final String brand;
        final String name;
        final Color color;
        final int speed;

        private CarBuilder( String brand, String name, Color color, int speed ) {
            this.brand = brand;
            this.name = name;
            this.color = color;
            this.speed = speed;
        }
        public CarBuilder brand( String newBrand ) {
            return new CarBuilder( newBrand, name, color, speed );
        }
        public CarBuilder name( String newName ) {
            return new CarBuilder( brand, newName, color, speed );
        }
        public CarBuilder color( Color newColor ) {
            return new CarBuilder( brand, name, newColor, speed );
        }
        public CarBuilder speed( int newSpeed ) {
            return new CarBuilder( brand, name, color, newSpeed );
        }
        public Car build() {
            return new Car( this );
        }
    }

    public static void main( String [] args ) {

        Car porsche = Car.with()
                .brand( "Porsche" )
                .name( "Carrera" )
                .color( Color.red )
                .speed( 270 )
                .build()
                ;

        // -- or with one default builder

        CarBuilder ASSEMBLY_LINE = Car.with()
                .brand( "Jeep" )
                .name( "Cherokee" )
                .color( Color.green )
                .speed( 180 )
                ;

        for( ;; ) ASSEMBLY_LINE.build();

        // -- or with custom default builder:

        CarBuilder MERCEDES = Car.with()
                .brand( "Mercedes" )
                .color( Color.black )
                ;

        Car c230 = MERCEDES.name( "C230" ).speed( 180 ).build(),
            clk = MERCEDES.name( "CLK" ).speed( 240 ).build();

    }
}

1

Każde rozwiązanie w Javie jest prawdopodobnie będzie dość rozwlekły, ale warto wspomnieć, że narzędzia takie jak Google AutoValues i Immutables wygeneruje konstruktora dla klasy automatycznie przy użyciu JDK czasie kompilacji przetwarzania adnotacji.

W moim przypadku chciałem użyć nazwanych parametrów w wyliczeniu Java, aby wzorzec konstruktora nie działał, ponieważ instancje wyliczenia nie mogą być tworzone przez inne klasy. Wymyśliłem podejście podobne do odpowiedzi @ deamon, ale dodaje sprawdzanie kolejności parametrów w czasie kompilacji (kosztem większej ilości kodu)

Oto kod klienta:

Person p = new Person( age(16), weight(100), heightInches(65) );

I realizacja:

class Person {
  static class TypedContainer<T> {
    T val;
    TypedContainer(T val) { this.val = val; }
  }
  static Age age(int age) { return new Age(age); }
  static class Age extends TypedContainer<Integer> {
    Age(Integer age) { super(age); }
  }
  static Weight weight(int weight) { return new Weight(weight); }
  static class Weight extends TypedContainer<Integer> {
    Weight(Integer weight) { super(weight); }
  }
  static Height heightInches(int height) { return new Height(height); }
  static class Height extends TypedContainer<Integer> {
    Height(Integer height) { super(height); }
  }

  private final int age;
  private final int weight;
  private final int height;

  Person(Age age, Weight weight, Height height) {
    this.age = age.val;
    this.weight = weight.val;
    this.height = height.val;
  }
  public int getAge() { return age; }
  public int getWeight() { return weight; }
  public int getHeight() { return height; }
}

0

Warto rozważyć idiom obsługiwany przez bibliotekę karg :

class Example {

    private static final Keyword<String> GREETING = Keyword.newKeyword();
    private static final Keyword<String> NAME = Keyword.newKeyword();

    public void greet(KeywordArgument...argArray) {
        KeywordArguments args = KeywordArguments.of(argArray);
        String greeting = GREETING.from(args, "Hello");
        String name = NAME.from(args, "World");
        System.out.println(String.format("%s, %s!", greeting, name));
    }

    public void sayHello() {
        greet();
    }

    public void sayGoodbye() {
        greet(GREETING.of("Goodbye");
    }

    public void campItUp() {
        greet(NAME.of("Sailor");
    }
}

Wydaje się, że jest to w zasadzie to samo, co R Cashaodpowiedź, ale bez kodu wyjaśniającego.
Scheintod

0

Oto wzorzec Konstruktora sprawdzany przez kompilator. Ostrzeżenia:

  • nie może to zapobiec podwójnemu przypisaniu argumentu
  • nie możesz mieć dobrej .build()metody
  • jeden parametr ogólny na pole

Potrzebujesz więc czegoś spoza klasy, co zakończy się niepowodzeniem, jeśli nie zostanie zaliczone Builder<Yes, Yes, Yes>. Zobacz getSummetodę statyczną jako przykład.

class No {}
class Yes {}

class Builder<K1, K2, K3> {
  int arg1, arg2, arg3;

  Builder() {}

  static Builder<No, No, No> make() {
    return new Builder<No, No, No>();
  }

  @SuppressWarnings("unchecked")
  Builder<Yes, K2, K3> arg1(int val) {
    arg1 = val;
    return (Builder<Yes, K2, K3>) this;
  }

  @SuppressWarnings("unchecked")
  Builder<K1, Yes, K3> arg2(int val) {
    arg2 = val;
    return (Builder<K1, Yes, K3>) this;
  }

  @SuppressWarnings("unchecked")
  Builder<K1, K2, Yes> arg3(int val) {
    this.arg3 = val;
    return (Builder<K1, K2, Yes>) this;
  }

  static int getSum(Builder<Yes, Yes, Yes> build) {
    return build.arg1 + build.arg2 + build.arg3;
  }

  public static void main(String[] args) {
    // Compiles!
    int v1 = getSum(make().arg1(44).arg3(22).arg2(11));
    // Builder.java:40: error: incompatible types:
    // Builder<Yes,No,Yes> cannot be converted to Builder<Yes,Yes,Yes>
    int v2 = getSum(make().arg1(44).arg3(22));
    System.out.println("Got: " + v1 + " and " + v2);
  }
}

Zastrzeżenia wyjaśnione . Dlaczego nie ma metody budowania? Problem w tym, że znajdzie się w Builderklasie i zostanie sparametryzowany za pomocą K1, K2, K3itp. Ponieważ sama metoda musi się kompilować, wszystko, co wywołuje, musi się kompilować. Zatem generalnie nie możemy umieścić testu kompilacji w metodzie samej klasy.

Z podobnego powodu nie możemy zapobiec podwójnemu przypisaniu za pomocą modelu konstruktora.


-1

@irreputable wymyśliło fajne rozwiązanie. Jednak - może to pozostawić twoją instancję klasy w nieprawidłowym stanie, ponieważ nie nastąpi weryfikacja ani sprawdzanie spójności. Dlatego wolę łączyć to z rozwiązaniem Builder, unikając tworzenia dodatkowej podklasy, chociaż nadal będzie to podklasa klasy builder. Dodatkowo, ponieważ dodatkowa klasa konstruktora sprawia, że ​​jest bardziej rozwlekły, dodałem jeszcze jedną metodę wykorzystującą lambdę. Dodałem kilka innych podejść do budowania dla kompletności.

Rozpoczynając od zajęć w następujący sposób:

public class Foo {
  static public class Builder {
    public int size;
    public Color color;
    public String name;
    public Builder() { size = 0; color = Color.RED; name = null; }
    private Builder self() { return this; }

    public Builder size(int size) {this.size = size; return self();}
    public Builder color(Color color) {this.color = color; return self();}
    public Builder name(String name) {this.name = name; return self();}

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

  private final int size;
  private final Color color;
  private final String name;

  public Foo(Builder b) {
    this.size = b.size;
    this.color = b.color;
    this.name = b.name;
  }

  public Foo(java.util.function.Consumer<Builder> bc) {
    Builder b = new Builder();
    bc.accept(b);
    this.size = b.size;
    this.color = b.color;
    this.name = b.name;
  }

  static public Builder with() {
    return new Builder();
  }

  public int getSize() { return this.size; }
  public Color getColor() { return this.color; }  
  public String getName() { return this.name; }  

}

Następnie za pomocą różnych metod:

Foo m1 = new Foo(
  new Foo.Builder ()
  .size(1)
  .color(BLUE)
  .name("Fred")
);

Foo m2 = new Foo.Builder()
  .size(1)
  .color(BLUE)
  .name("Fred")
  .build();

Foo m3 = Foo.with()
  .size(1)
  .color(BLUE)
  .name("Fred")
  .build();

Foo m4 = new Foo(
  new Foo.Builder() {{
    size = 1;
    color = BLUE;
    name = "Fred";
  }}
);

Foo m5 = new Foo(
  (b)->{
    b.size = 1;
    b.color = BLUE;
    b.name = "Fred";
  }
);

Wygląda na to, że po części jest to całkowite zdzierstwo z tego, co @LaurenceGonsalves już opublikował, ale zobaczysz małą różnicę w wybranej konwencji.

Zastanawiam się, gdyby JLS kiedykolwiek zaimplementował nazwane parametry, jak by to zrobili? Czy rozszerzyliby jeden z istniejących idiomów, zapewniając mu krótkie wsparcie? W jaki sposób Scala obsługuje nazwane parametry?

Hmmm - wystarczy do zbadania, a może nowe pytanie.

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.