Это плохая практика, чтобы заставить сеттер возвращать «это»?

190

Хорошая или плохая идея сделать сеттеры в возврате 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 для создания объекта.

  • 0
    Так как я видел этот шаблон дизайна некоторое время назад, я делаю это везде, где это возможно. Если метод явно не должен возвращать что-либо для выполнения своей работы, он теперь возвращает this . Иногда я даже изменяю функцию так, чтобы вместо возврата значения она работала с членом объекта, просто чтобы я мог это сделать. Это замечательно. :)
  • 4
    Для телескопических сеттеров, возвращающих себя и в сборщиках, я предпочитаю использовать withName (имя строки) вместо setName (имя строки). Как вы указали, обычной практикой и ожиданием сеттера является возврат void. «Нестандартные» сеттеры могут плохо работать с существующими средами, например, менеджерами сущностей JPA, Spring и т. Д.
Показать ещё 2 комментария
Теги:
oop
design-patterns

25 ответов

50
Лучший ответ

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

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

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

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

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

  • 1
    ну, в общем, вы все равно ничего не возвращаете от сеттера.
  • 15
    Может быть, не для начала, но сеттер не обязательно сохраняет свое первоначальное назначение. То, что раньше было переменной, может измениться в состояние, которое охватывает несколько переменных или иметь другие побочные эффекты. Некоторые установщики могут возвращать предыдущее значение, другие могут возвращать индикатор сбоя, если сбой слишком распространен для исключения. Это поднимает еще один интересный момент: что, если инструмент / среда, которую вы используете, не распознает ваши сеттеры, когда они возвращают значения?
Показать ещё 8 комментариев
81

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

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

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

String s = new StringBuilder().append("testing ").append(1)
  .append(" 2 ").append(3).toString();
  • 23
    Он часто используется в сборщиках, но я бы не сказал «это ... называется шаблоном строителей».
  • 9
    Мне смешно, что некоторые обоснования для свободных интерфейсов в том, что они делают код легче для чтения. Я мог видеть, что писать удобнее, но мне кажется, что читать сложнее. Это единственное настоящее несогласие с этим.
Показать ещё 9 комментариев
67

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

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

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

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

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

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

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

  • 4
    Кстати, это «свободно», а не «плавно» ... так как позволяет структурировать серию вызовов методов, как разговорный язык.
  • 0
    Последний пункт об неизменности очень важен. Самый простой пример - String. Разработчики Java ожидают, что при использовании методов в String они получают совершенно новый экземпляр, а не тот же, но измененный экземпляр. При свободном интерфейсе в документации метода должно быть упомянуто, что возвращаемый объект - это «this» вместо нового экземпляра.
Показать ещё 3 комментария
60

Я предпочитаю использовать для этого методы '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!"));
  • 14
    +1 За интересное соглашение. Я не собираюсь применять его в своем собственном коде, так как кажется, что теперь вам нужно иметь get , set и a with для каждого поля класса. Это все еще интересное решение, хотя. :)
  • 1
    Это зависит от того, как часто вы вызываете сеттеров. Я обнаружил, что если эти сеттеры часто вызывают, их стоит добавить, так как это упрощает код повсюду. YMMV
Показать ещё 9 комментариев
17

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

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

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

list.Add(new Employee() {
    Name = "Jack Sparrow",
    Id = 1,
    Foo = "bacon!"
});
  • 13
    инициализация двойной скобки может иметь проблемы с equals, потому что она создает анонимный внутренний класс; см. c2.com/cgi/wiki?DoubleBraceInitialization
  • 0
    @ Csaba_H Ясно, что эта проблема - ошибка человека, который испортил метод equals . Есть очень простые способы справиться с анонимными классами на equals если вы знаете, что делаете.
Показать ещё 2 комментария
7

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

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

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

list.add(
    new Employee("Jack Sparrow")
    .Id(1)
    .foo("bacon!"));
По крайней мере, я думаю. Но вы можете сфокусировать меня и назвать меня ужасным программистом, если хотите. И я не знаю, разрешено ли вам делать это на Java.
  • 0
    «Еще лучше» плохо подходит для функциональности Format Source code, доступной в современных IDE. К несчастью.
  • 0
    Вы, вероятно, правы ... Единственный автоформатер, который я использовал, - это автоматический отступ в Emacs.
Показать ещё 2 комментария
6

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

  • 2
    Было бы здорово, если бы у Java был какой-то способ перегрузки по типу возвращаемого значения, а не только по JVM. Вы можете легко обойти многие из этих критических изменений.
  • 1
    Вы всегда можете определить функциональный интерфейс, который расширяет и Consumer<A> и Function<A,B> , предоставив реализацию по умолчанию void accept(A a) { apply(a); } Тогда он может легко использоваться как один из них и не будет нарушать любой код, который требует определенной формы.
6

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

  • 0
    Это также имеет значение, если вы используете бин-ориентированные среды, такие как Spring.
  • 1
    или если вы используете API интроспекции для написания рефлексивного кода ...
5

Это совсем не плохая практика. Но это не совместимо с 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();
  • 1
    Мое редактирование отклонено, но ваш пример Builder неверен. Во-первых, .value () ничего не возвращает, и даже не устанавливает поле some . Во-вторых, вы должны добавить защиту и установить для some значение null в build (), чтобы Some действительно неизменяемыми, в противном случае вы могли бы builder.value() вызвать builder.value() в том же экземпляре Builder. И, наконец, да, у вас есть конструктор, но у Some прежнему есть публичный конструктор, что означает, что вы открыто не поддерживаете использование построителя, т. Е. Пользователь не знает об этом, кроме как путем попытки или поиска метода для установки пользовательское value на всех.
  • 0
    @ Adowrath, если ответ неправильный, вы должны написать свой собственный ответ, а не пытаться редактировать чужой в форме
Показать ещё 5 комментариев
4

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

4

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

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

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

  • 10
    Интересно ... не могли бы вы рассказать об этом немного?
  • 0
    Согласен, я хотел бы узнать больше о том, как это может повлиять на оптимизацию.
Показать ещё 6 комментариев
3

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

2

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

2

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

Причины:

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

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

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

  • 2
    Я согласен на побочный эффект. Сеттеры, которые возвращают вещи, нарушают их имя. Вы настраиваете foo, но получаете объект обратно? Это новый объект или я изменил старый?
1

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

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

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;
    }
}
  • 0
    Это именно то, что шаблон Builder был разработан, чтобы исправить. Он не ломает лямбды в Java 8, не ломает привередливые инструменты JavaBeans и не вызывает проблем оптимизации в JVM (так как объект существует только во время создания экземпляра). Это также решает проблему «слишком большого числа конструкторов», которую вы получаете, просто не используя компоновщики, а также устраняет загрязнение кучи в анонимных классах с двойными скобками.
1

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

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

1

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

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

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;
    }
}
  • 1
    Есть несколько других компиляторов Java, кроме Eclipse, которые этого не принимают :). По сути, вы работаете с птицей системы типов Java (которая статична, а не динамична, как некоторые языки сценариев): вам придется разыграть то, что выходит из setName (), другу, прежде чем вы сможете установить на нем setNickName (). Так что, к сожалению, для иерархий наследования это устраняет большую часть преимуществ читабельности и делает эту потенциально потенциально полезную технику не очень полезной.
  • 5
    Используйте дженерики. class Chainable <Self extends Chainable> {public Self doSomething () {return (Self) this;}} Это технически не безопасно для типов (будет приведено приведение классов, если вы реализуете класс с типом Self, к которому вы не можете обращаться), но это правильная грамматика, и подклассы будут возвращать свой собственный тип.
Показать ещё 1 комментарий
1

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

  • 2
    Как насчет использования исключений для обозначения состояния ошибки? Коды ошибок можно легко проигнорировать, как мучительно усвоили многие программисты на Си. Исключения могут пузырить стек до точки, где они могут быть обработаны.
  • 0
    Исключения обычно предпочтительнее, но коды ошибок также полезны, когда вы не можете использовать исключения.
Показать ещё 1 комментарий
1

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

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

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

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

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

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

  • 1
    Нет, это все еще ужасно. С точки зрения обслуживания, последний лучше, потому что его легче читать. Кроме того, автоматические средства проверки кода, такие как CheckStyle, принудительно заставят строки по умолчанию составлять 80 символов - в любом случае код будет перенесен, что усугубит проблему читаемости / сопровождения. И наконец - это Java; нет никакой выгоды писать все в одной строке, если она все равно будет скомпилирована в байт-код.
  • 1
    лично я думаю, что первое легче читать, особенно если вы создаете несколько объектов таким образом.
Показать ещё 3 комментария
0

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

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

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

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

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

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

Повторение переменной 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 нужно было бы настроить, чтобы сделать это правильно для некоторых ситуаций, таких как наследование и инструменты.

0

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

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

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

0

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

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); 
  • 8
    Я думаю, что это довольно читабельно, если вы не пытаетесь поместить весь код в одну строку.
0

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

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

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

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

-2

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

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

setPropertyFromParams(array $hashParamList) { ... }
  • 0
    не подходит для автозаполнения, рефакторинга и читабельности и стандартного кода
  • 0
    Потому что: 1) Вы должны помнить порядок или читать его из документов, 2) вы теряете всю безопасность типов во время компиляции, 3) вы должны приводить значения (это и 2), конечно, не проблема в динамическом язык, конечно), 4) вы не можете расширить это полезно, кроме проверки длины массива при каждом вызове, и 5) если вы что-то измените, будь то порядок или даже наличие определенных значений, вы не можете проверить, что ни время выполнения ни компилировать timewithout сомнений. С сеттерами: 1), 2), 3): не проблема. 4) Добавление новых методов ничего не нарушает. 5) явное сообщение об ошибке.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню