Ідіома параметра з іменем у Java


81

Як реалізувати ідіому іменованого параметра в Java? (особливо для конструкторів)

Я шукаю синтаксис, схожий на Objective-C, а не такий, який використовується в JavaBeans.

Невеликий приклад коду буде непоганим.

Дякую.

Відповіді:


105

Найкращою ідіомою Java для моделювання аргументів ключових слів у конструкторах є шаблон Builder, описаний у Effective Java 2nd Edition .

Основна ідея полягає в тому, щоб мати клас Builder, який має сеттери (але зазвичай не геттери) для різних параметрів конструктора. Існує також build()метод. Клас Builder часто є (статичним) вкладеним класом класу, який він використовується для побудови. Конструктор зовнішнього класу часто є приватним.

Кінцевий результат виглядає приблизно так:

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...
}

Щоб створити екземпляр Foo, ви пишете щось на зразок:

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

Основні застереження:

  1. Налаштування шаблону досить багатослівне (як бачите). Можливо, не варто того, крім занять, які ви плануєте створити в багатьох місцях.
  2. Немає перевірки під час компіляції, що всі параметри були вказані рівно один раз. Ви можете додати перевірки виконання, або ви можете використовувати це лише для необов’язкових параметрів і зробити необхідні параметри нормальними параметрами або для Foo, або у конструкторі Builder. (Люди зазвичай не турбуються у випадку, коли один і той же параметр встановлюється кілька разів.)

Ви також можете переглянути цю публікацію в блозі (не я).


12
Це насправді не називаються параметри так, як це робить Objective-C. Це більше схоже на вільний інтерфейс. Це насправді не одне і те ж.
Асаф,

30
Мені подобається використовувати .withFoo, а не .setFoo: newBuilder().withSize(1).withName(1).build()а неnewBuilder().setSize(1).setName(1).build()
notnoop

17
Асаф: так, я знаю. Java не має іменованих параметрів. Ось чому я сказав, що це "найкраща ідіома Java, якою я імітую для моделювання аргументів ключових слів". "Іменовані параметри" Objective-C також менш ідеальні, оскільки вони змушують певне впорядкування. Вони не є справжніми аргументами ключових слів, як у Lisp або Python. Принаймні за допомогою шаблону Java Builder вам просто потрібно запам'ятати імена, а не порядок, як і справжні аргументи ключових слів.
Лоуренс Гонсалвес,

14
notnoop: Я віддаю перевагу "set", оскільки це сетери, які мутують стан Builder. Так, "з" виглядає добре в тому простому випадку, коли ви поєднуєте все, але в більш складних випадках, коли у вас є Builder у власній змінній (можливо, тому, що ви умовно встановлюєте властивості), мені подобається, що набір префікс повністю дає зрозуміти, що Builder мутується, коли викликаються ці методи. Префікс "with" для мене звучить функціонально, і ці методи явно не функціональні.
Лоуренс Гонсалвес,

4
There's no compile-time checking that all of the parameters have been specified exactly once.Цю проблему можна подолати, повернувши інтерфейси Builder1туди, BuilderNде кожен охоплює або одного із установників, або build(). Кодувати набагато детальніше, але він має підтримку компілятора для вашого DSL і робить з автозаповненням дуже приємно працювати.
rsp

73

Про це варто згадати:

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

так званий подвійний фігурний ініціалізатор . Це насправді анонімний клас з ініціалізатором екземпляра.


26
Цікава техніка, але здається трохи дорогою, оскільки вона створюватиме новий клас кожного разу, коли я використовую її у своєму коді.
Red Hyena

6
Окрім попередження про автоматичне форматування, підкласи та серіалізацію, це насправді досить близько до синтаксису C # для ініціалізації на основі властивостей. Однак C # станом на 4.0 також має названі параметри, тому програмісти дійсно зіпсовані для вибору, на відміну від програмістів Java, яким доводиться імітувати ідіоми, що заважають їм пізніше стріляти собі в ногу.
Distortum

3
Радий бачити, що це можливо, але мені довелося проголосувати проти, оскільки це рішення є дорогим, як зазначила Червона Гієна. Не можу дочекатися, поки Java насправді підтримує іменовані параметри, як це робить Python.
Gattster

12
Проголосувати. Це відповідає на питання найбільш читабельним, стислим способом. Прекрасно. Це "неефективно". Про скільки зайвих мілісекунд і бітів ми тут говоримо? Хтось із них? Десятки? Не зрозумійте мене неправильно - я не буду цим користуватися, бо я б ненавидів стратити багатослівним гайкою Java (каламбур призначений;)
Стів

2
Ідеально! Потрібні лише загальнодоступні / захищені поля. Це, безумовно, найкраще рішення, що спричиняє набагато менше накладних витрат, ніж будівельник. Гієна / Гаттстер: будь ласка (1) прочитайте JLS та (2) перевірте згенерований байт-код перед тим, як писати свої коментарі.
Jonatan Kaźmierczak

21

Ви також можете спробувати дотримуватися порад звідси: http://www.artima.com/weblogs/viewpost.jsp?thread=118828

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

Це багатослівно на сайті дзвінків, але загалом дає найнижчі накладні витрати.


3
Хороша причина низьких накладних витрат, але це відчуває себе настільки хакерським. Я, ймовірно, буду використовувати метод Builder () для випадків, коли існує багато аргументів.
Gattster

23
Я думаю, що це повністю втрачає сенс названих параметрів. (що має щось асоціювати імена зі значеннями ). Там немає ніяких ознак взагалі , якщо в зворотному порядку. Замість цього я порадив би просто додати коментар:doIt( /*value*/ 13, /*location*/ 47, /*overwrite*/ true )
Scheintod

20

Стиль 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);

    }
}
  • названі параметри
  • виправити порядок аргументів
  • статична перевірка -> неможлива безіменна особа
  • важко випадково змінити аргументи одного типу (як це можливо у конструкторах телескопів)

3
приємно. Яке безглузде воно не має змінного порядку аргументів. (Але не сказати, що я б цим скористався ...)
Scheintod

1
Це геніальна ідея. Визначення create()зупинило мене на шляху. Я ніколи не бачив такого стилю лямбда-ланцюгів на Java. Ви вперше виявили цю ідею іншою мовою з лямбдами?
kevinarpe

2
Це називається каррі: en.wikipedia.org/wiki/Currying . До речі: Можливо, це розумна ідея, але я б не рекомендував цей названий стиль аргументів. Я перевірив його в реальному проекті з безліччю аргументів, і це призводить до важкого для читання та важкого для навігації коду.
Алекс

Зрештою, Java отримає параметри стилю Visual Basic з іменами. Java раніше не робила, тому що C ++ ні. Але ми врешті дійдемо туди. Я б сказав, що 90% поліморфізму Java - це просто злом додаткових параметрів.
Змінний

7

Java не підтримує подібні до Objective-C іменовані параметри конструкторів або аргументів методів. Більше того, це насправді не спосіб Java. У Java типовий шаблон - багатослівні імена класів та членів. Класи та змінні повинні бути іменниками, а названий метод - дієсловами. Я припускаю, що ви могли б проявити креативність і відхилитися від конвенцій про іменування Java та наслідувати парадигму Objective-C, але це не було б особливо оцінено пересічним розробником Java, відповідальним за підтримку вашого коду. Працюючи будь-якою мовою, вам слід дотримуватися умовних норм мови та спільноти, особливо під час роботи в команді.


4
+1 - за пораду щодо дотримання ідіом мови, якою ви зараз користуєтесь. Будь ласка, подумайте про інших людей, яким потрібно буде прочитати ваш код!
Stephen C

3
Я підтримав вашу відповідь, тому що, на мою думку, ви зробили добру думку. Якщо мені довелося здогадуватися, чому ви отримали голос проти, це, мабуть, тому, що це не відповідає на питання. Питання: "Як мені зробити іменовані параметри в Java?" В: "Ти не робиш"
Гаттстер

12
Я проголосував проти, бо думаю, що ваша відповідь не має нічого спільного із запитанням. Багатослівні імена насправді не вирішують проблему впорядкування параметрів. Так, ви можете закодувати їх у назві, але це явно неможливо. Виховання не пов’язаної парадигми не пояснює, чому одна парадигма не підтримується.
Андреас Мюллер,

7

Якщо ви використовуєте Java 6, ви можете використовувати змінні параметри та імпортувати статичні, щоб отримати набагато кращий результат. Детально про це можна ознайомитись у:

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

Коротше, ви могли б мати щось на зразок:

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

2
Мені це подобається, але це все одно вирішує лише половину проблеми. У Java ви не можете запобігти випадково транспонованим параметрам, не втрачаючи перевірку часу компіляції для необхідних значень.
cdunn2001,

Без безпеки типу це гірше, ніж прості // коментарі.
Пітер Девіс,

7

Я хотів би зазначити, що цей стиль звертається як до названого параметра, так і до властивостей без префікса get і set, який має інша мова. Це не звично в області Java, але простіше, не важко зрозуміти, особливо якщо ви працювали з іншими мовами.

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

Що стосовно

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 набагато кращий, оскільки ви можете перевірити деякі обмеження на build (). Але я також віддаю перевагу коротшим аргументам без набору / з префіксом.
rkj

4
Крім того, шаблон конструктора кращий, оскільки він дозволяє зробити вбудований клас (у цьому випадку Тигр) незмінним.
Джефф Олсон,

2

Ви можете використовувати звичайний конструктор та статичні методи, які дають аргументам назву:

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;
    }

}

Використання:

import static Something.*;

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

Обмеження порівняно з реальними іменованими параметрами:

  • порядок аргументів є актуальним
  • списки змінних аргументів неможливі з одним конструктором
  • вам потрібен метод для кожного аргументу
  • насправді не краще, ніж коментар (нове Щось ( /*name*/ "pen", /*size*/ 20, /*weight*/ 8.2))

Якщо у вас є вибір, подивіться на Scala 2.8. http://www.scala-lang.org/node/2075


2
Недоліком такого підходу є те, що ви повинні отримувати аргументи у правильному порядку. Вищезазначений код дозволить вам написати: Something s = new Something (назва ("ручка"), size (20), size (21)); Крім того, такий підхід не допомагає уникнути введення необов’язкових аргументів.
Matt Quail

1
Я б підтримав це для аналізу: not really better than a comment... з іншого боку ...;)
Scheintod

2

Використовуючи лямбди Java 8, ви можете наблизитись до реальних іменованих параметрів.

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

Зверніть увагу, що це, ймовірно, порушує пару десятків "найкращих практик Java" (як і все, що використовує $символ).

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));
  }
}

Плюси:

  • Значно коротший за будь-який зразок будівельника, який я бачив до цього часу
  • Працює як для методів, так і для конструкторів
  • Цілком безпечний тип
  • Це виглядає дуже близьким до фактичних іменованих параметрів в інших мовах програмування
  • Це приблизно так само безпечно, як і ваш типовий шаблон конструктора (може встановлювати параметри кілька разів)

Мінуси:

  • Ваш начальник, мабуть, вас за це лінчуватиме
  • Складніше сказати, що відбувається

1
Мінуси: поля публічні та не остаточні. Якщо у вас це добре, чому б просто не використовувати сетери? Як це працює для методів?
Олексій

Могли б використовувати сетери, але який сенс у цьому? Це просто робить код довшим, і це позбавить вигоди від такого. Призначення є побічним ефектом, а сетери - чорними ящиками. $fooніколи не втікає до абонента (якщо хтось не призначає його змінній всередині зворотного виклику), то чому вони не можуть бути загальнодоступними?
Vic

2

Ви можете використовувати анотацію @Builder проекту Lombok для моделювання названих параметрів у Java. Це створить для вас конструктор, який ви можете використовувати для створення нових екземплярів будь-якого класу (як класів, які ви написали, так і тих, що надходять із зовнішніх бібліотек).

Ось як увімкнути його в класі:

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

Згодом ви можете використовувати це шляхом:

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

Якщо ви хочете створити такий Builder для класу, що надходить з бібліотеки, створіть анотований статичний метод, такий як:

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

Це створить метод з іменем "builder", який можна викликати:

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

Google auto / value має подібні цілі, але використовує фреймворк обробки анотацій, який набагато безпечніший (матеріали все одно працюватимуть після оновлення JVM), ніж маніпулювання байт-кодом проекту Lombocks.
Рене

1
Я думаю, що Google auto / value вимагає додаткової милі порівняно з використанням Lombok. Підхід Ломбока більш-менш сумісний із традиційним написанням JavaBean (наприклад, ви можете створити екземпляр за допомогою нового, поля відображаються належним чином у налагоджувачі тощо). Я також не великий шанувальник рішення байтового коду manpiluation + IDE плагіна, що використовує Lombok, але мушу визнати, що на практиці це працює нормально. Наразі жодних проблем із зміною версії JDK, роздумами тощо
Іштван Девай

Так це правда. Для auto / value потрібно вказати абстрактний клас, який потім буде реалізовано. Ломбоку потрібно набагато менше коду. Тож потрібно порівняти плюси і мінуси.
Рене

2

Я відчуваю, що "обхідний шлях" заслуговує на власну відповідь (прихований у існуючих відповідях і згаданий у коментарях тут).

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

1

Це варіант BuilderВізерунка, як описано Лоуренсом вище.

Я дуже часто цим користуюся (у відповідних місцях).

Головна відмінність полягає в тому, що в цьому випадку Builder є незмінним . Це має ту перевагу, що його можна використовувати повторно і він безпечний для потоків.

Таким чином, ви можете використовувати це для створення одного конструктора за замовчуванням, а потім у різних місцях, де він вам потрібен, ви можете налаштувати його та побудувати свій об’єкт.

Це має найбільший сенс, якщо ви будуєте один і той самий об'єкт знову і знову, тому що тоді ви можете зробити конструктор статичним і вам не доведеться турбуватися про зміну його налаштувань.

З іншого боку, якщо вам доводиться будувати об'єкти зі змінними параметрами, це має деякі тихі витрати. (але привіт, ви можете поєднувати статичне / динамічне генерування зі спеціальними buildметодами)

Ось приклад коду:

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

Будь-яке рішення в Java, швидше за все , буде досить багатослівний, але варто відзначити , що такі інструменти , як Google AutoValues і Immutables буде генерувати будівельник класи для вас автоматично з допомогою JDK час компіляції обробки анотацій.

У моєму випадку я хотів, щоб іменовані параметри використовувались у переліченні Java, тому шаблон побудови не буде працювати, оскільки екземпляри перерахування не можуть бути створені іншими класами. Я придумав підхід, подібний до відповіді @ deamon, але додає перевірку впорядкування параметрів під час компіляції (за рахунок додаткового коду)

Ось код клієнта:

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

І реалізація:

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

Можливо, варто розглянути ідіому, яку підтримує бібліотека 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");
    }
}

Здається, це в основному те саме, що і R Cashaвідповідь, але без коду, який би це пояснив.
Scheintod

0

Ось шаблон перевірки компілятора Builder. Застереження:

  • це не може запобігти подвійному присвоєнню аргументу
  • ви не можете мати хороший .build()метод
  • один загальний параметр на поле

Тож вам потрібно щось поза класом, яке зазнає невдачі, якщо не буде пройдено Builder<Yes, Yes, Yes>. Див. getSumСтатичний метод як приклад.

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);
  }
}

Пояснив застереження . Чому немає методу побудови? Проблема в тому, що він буде в Builderкласі, і він буде параметризований за допомогою K1, K2, K3і т. Д. Оскільки сам метод повинен компілювати, все, що він викликає, має скомпілювати. Отже, загалом ми не можемо поставити тест компіляції в метод самого класу.

З подібної причини ми не можемо запобігти подвійному призначенню, використовуючи модель конструктора.


-1

@irreputable придумав гарне рішення. Однак - це може залишити ваш примірник класу в недійсному стані, оскільки перевірка та перевірка узгодженості не відбуватимуться. Тому я вважаю за краще поєднувати це з рішенням Builder, уникаючи створення додаткового підкласу, хоча це все одно буде підклас класу builder. Крім того, оскільки додатковий клас будівельника робить його більш багатослівним, я додав ще один метод, використовуючи лямбда. Для повноти я додав деякі інші підходи до будівництва.

Починаючи з класу наступним чином:

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; }  

}

Потім використовуючи це, застосовуючи різні методи:

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";
  }
);

Це виглядає частково повним викраданням із того, що @LaurenceGonsalves вже розмістило, але ви побачите невелику різницю у вибраній конвенції.

Мені цікаво, якби JLS коли-небудь реалізовував названі параметри, як би вони це робили? Чи будуть вони поширюватися на одну з існуючих ідіом, надаючи коротку підтримку? Також як Scala підтримує іменовані параметри?

Хммм - досить для дослідження, і, можливо, нове питання.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.