Неправильная ли практика заставить сеттер вернуться "this"?

Хорошая или плохая идея сделать сеттеры в возврате java "this"?

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

Этот шаблон может быть полезен, потому что тогда вы можете настроить сеттеры следующим образом:

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

вместо этого:

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

... но это похоже на стандартную конвенцию. Полагаю, что это может быть полезно только потому, что это может заставить этого сеттера сделать что-то еще полезное. Я видел, что этот шаблон использовал некоторые места (например, JMock, JPA), но он кажется необычным и обычно используется только для очень хорошо определенных API, где этот шаблон используется повсюду.

Update:

То, что я описал, очевидно, действительно, но то, что я действительно ищу, - это некоторые мысли о том, является ли это вообще приемлемым, и если есть какие-либо подводные камни или соответствующие передовые методы. Я знаю о шаблоне Builder, но это немного больше связано с тем, что я описываю - как описывает Джош Блох, существует связанный с ним статический класс Builder для создания объекта.

Ответ 1

Я не думаю, что с ним что-то не так, но это просто вопрос стиля. Это полезно, если:

  • Вам нужно установить сразу несколько полей (в том числе при построении)
  • Вы знаете, какие поля вам нужно установить во время написания кода, и
  • существует множество разных комбинаций, для которых вы хотите установить поля.

Альтернативой этому методу может быть:

  • Один мега конструктор (недостаток: вы можете передать много значений null или значений по умолчанию, и становится трудно узнать, какое значение соответствует тому, что)
  • Несколько перегруженных конструкторов (недостаток: становится громоздким, если у вас есть несколько)
  • Factory/статические методы (downside: то же, что и перегруженные конструкторы - становятся громоздкими, если их больше нескольких)

Если вы только собираетесь установить несколько свойств одновременно, я бы сказал, что не стоит возвращать 'this'. Это, безусловно, падает, если позже вы решите вернуть что-то еще, например, индикатор состояния/успеха/сообщение.

Ответ 2

Это не плохая практика. Это все более распространенная практика. Большинство языков не требуют, чтобы вы имели дело с возвращенным объектом, если вы не хотите, чтобы он не менял "нормальный" синтаксис использования сеттера, но позволяет объединять сеттеры вместе.

Это обычно называется шаблоном компоновщика или свободным интерфейсом .

Он также распространен в Java API:

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

Ответ 3

Подводя итог:

  • он называется "беглый интерфейс" или "цепочка методов".
  • это не "стандартная" Java, хотя вы видите ее больше в эти дни (отлично работает в jQuery)
  • он нарушает спецификацию JavaBean, поэтому он будет работать с различными инструментами и библиотеками, особенно с JSP-конструкторами и Spring.
  • он может предотвратить некоторые оптимизации, которые JVM обычно выполняет
  • некоторые люди думают, что он очищает код, другие считают его "ужасным".

Пара других пунктов, не упомянутых:

  • Это нарушает принципала, что каждая функция должна делать одну (и только одну) вещь. Вы можете или не верите в это, но в Java я считаю, что он работает хорошо.

  • IDE не собираются генерировать их для вас (по умолчанию).

  • Я, наконец, здесь реальная точка данных. У меня возникли проблемы с использованием библиотеки, подобной этой. Hibernate query builder - пример этого в существующей библиотеке. Поскольку методы Query set * возвращают запросы, невозможно сказать, просто взглянув на подпись, как ее использовать. Например:

    Query setWhatever(String what);
    
  • Он вводит двусмысленность: метод модифицирует текущий объект (ваш шаблон) или, возможно, Query действительно неизменен (очень популярный и ценный шаблон), и метод возвращает новый. Это просто делает библиотеку сложнее в использовании, и многие программисты не используют эту функцию. Если сеттеры были сеттерами, было бы более ясным, как его использовать.

Ответ 4

Я предпочитаю использовать для этого методы 'with':

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

Таким образом:

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

Ответ 5

Если вы не хотите возвращать 'this' из сеттера, но не хотите использовать второй вариант, вы можете использовать следующий синтаксис для установки свойств:

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

Как в стороне, я думаю, что он немного чище в С#:

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

Ответ 6

Я не знаю Java, но я сделал это на С++. Другие люди говорят, что это делает линии действительно длинными и очень трудными для чтения, но я делал это так много раз:

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

Это еще лучше:

list.add(
    new Employee("Jack Sparrow")
    .Id(1)
    .foo("bacon!"));
По крайней мере, я думаю. Но вы можете сфокусировать меня и назвать меня ужасным программистом, если хотите. И я не знаю, разрешено ли вам делать это на Java.

Ответ 7

Поскольку он не возвращает void, он больше не является корректным средством определения свойств JavaBean. Это может иметь значение, если вы один из семи человек в мире, используя визуальные инструменты "Bean Builder", или один из 17, используя элементы JSP- bean -setProperty.

Ответ 8

Он не только нарушает конвенцию getters/seters, но и нарушает структуру ссылок на Java 8. MyClass::setMyValue - это BiConsumer<MyClass,MyValue>, а myInstance::setMyValue - Consumer<MyValue>. Если у вас есть ваш setter return this, то он больше не является допустимым экземпляром Consumer<MyValue>, а скорее Function<MyValue,MyClass>, и будет вызывать что-либо, используя ссылки на методы для этих сеттеров (при условии, что они являются недействительными)./p >

Ответ 9

Это совсем не плохая практика. Но это не совместимо с JavaBeans Spec.

И многие спецификации зависят от этих стандартных аксессуаров.

Вы всегда можете заставить их сосуществовать друг с другом.

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

Теперь мы можем использовать их вместе.

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

Вот еще одна версия для неизменяемого объекта.

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

Теперь мы можем это сделать.

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

Ответ 10

Эта схема (каламбур), называемая "плавным интерфейсом", сейчас становится довольно популярной. Это приемлемо, но это не моя чашка чая.

Ответ 11

Как минимум в теории, это может повредить механизмы оптимизации JVM, установив ложные зависимости между вызовами.

Предполагается, что это синтаксический сахар, но на самом деле может создавать побочные эффекты в супер-интеллектуальной виртуальной машине Java 43.

Вот почему я голосую нет, не используйте его.

Ответ 12

Я выступаю за сеттеры, у которых "this" возвращается. Меня не волнует, не соответствует ли это beans. Для меня, если все в порядке, чтобы иметь выражение "=" выражение /, то сеттеры, возвращающие значения, прекрасны.

Ответ 13

Я предпочитал этот подход, но я решил не принимать его.

Причины:

  • читабельность. Это делает код более удобочитаемым для каждого setFoo() на отдельной строке. Вы обычно читаете код много, много раз, чем один раз, когда вы его пишете.
  • Побочный эффект: setFoo() должен устанавливать только поле foo, ничего больше. Возвращение это дополнительное "ЧТО было".

Образец Builder, который я видел, не использует соглашение setFoo (foo).setBar(bar), но больше foo (foo).bar(bar). Возможно, именно по этим причинам.

Это, как всегда, вопрос вкуса. Мне просто нравится подход "наименьших неожиданностей".

Ответ 14

Paulo Abrantes предлагает другой способ сделать JavaBean seters бегло: определить внутренний класс строителя для каждого JavaBean. Если вы используете инструменты, которые сбрасываются сеттерами, возвращающими значения, шаблон Пауло может помочь.

Ответ 15

На первый взгляд: "Грязно!".

О дальнейшей мысли

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

на самом деле менее подвержен ошибкам, чем

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

Так интересно. Добавление идеи в панель инструментов...

Ответ 16

В целом это хорошая практика, но вам может потребоваться, чтобы функции set-type использовали логический тип, чтобы определить, была ли операция успешно завершена или нет, это тоже один из способов. В общем, нет догмы, чтобы сказать, что это хорошо или кровать, конечно, это происходит из ситуации.

Ответ 17

Да, я думаю, что это хорошая идея.

Если бы я мог что-то добавить, как насчет этой проблемы:

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

Это будет работать:

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

Это не будет принято Eclipse!

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

Это связано с тем, что setName() возвращает "Люди", а не "Друг", и нет "PeoplesetNickName".

Как мы можем написать сеттеры для возврата класса SELF вместо имени класса?

Что-то вроде этого было бы хорошо (если бы существовало ключевое слово SELF). Все это существует?

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

Ответ 18

Эта конкретная модель называется цепочкой методов. ссылка Википедии, у этого есть больше объяснений и примеров того, как это делается на разных языках программирования.

P.S: Просто подумал оставить его здесь, так как я искал конкретное имя.

Ответ 19

Если вы используете одно и то же соглашение в целом, это кажется прекрасным.

В противном случае, если существующая часть вашего приложения использует стандартное соглашение, я бы придерживался его и добавлял сборщики к более сложным классам

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

Ответ 20

Из утверждения

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

Я вижу две вещи

1) Бессмысленное утверждение. 2) Отсутствие читаемости.

Ответ 21

Это может быть менее читаемым

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

или

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

Это более читаемо, чем:

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

Ответ 22

Я создавал свои сеттеры довольно долго, и единственная реальная проблема связана с библиотеками, которые придерживаются строгих getPropertyDescriptors, чтобы получить bean читателей/писателей bean. В этих случаях ваша java "bean" не будет иметь нареканий, которые вы ожидаете.

Например, я не проверял его точно, но я не удивлюсь, что Джексон не узнает их как сеттеров при создании вами java-объектов из json/maps. Надеюсь, я ошибаюсь в этом вопросе (я скоро проверю).

На самом деле, я разрабатываю легкую SQL-ориентированную ORM, и мне нужно добавить некоторый код beyong getPropertyDescriptors к признанным сеттерам, который возвращает это.

Ответ 23

Давно ответил, но мои два цента... Это прекрасно. Я бы хотел, чтобы этот свободный интерфейс использовался чаще.

Повторение переменной factory не добавляет больше информации ниже:

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

Это чище, imho:

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

Конечно, как один из ответов, уже упомянутых, Java API нужно было бы настроить, чтобы сделать это правильно для некоторых ситуаций, таких как наследование и инструменты.

Ответ 24

Лучше использовать другие языковые конструкции, если они доступны. Например, в Котлине вы будете использовать, применять или допускать. Если вы используете этот подход, вам не нужно будет возвращать экземпляр из вашего сеттера.

Этот подход позволяет вашему клиентскому коду быть:

  • Безразлично к типу возврата
  • Легче поддерживать
  • Избегайте побочных эффектов компилятора

Вот несколько примеров.

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

Ответ 25

Плохая привычка: набор сеттеров getter get

как явно объявить метод, который делает это для U

setPropertyFromParams(array $hashParamList) { ... }