Цепочка методов в Java [закрыта]

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

Например, если бы мне нужно было создать класс Car, я мог бы сделать его связным, повторно задав this вместо void следующим образом:

public class Car {
    private String make;        

    public Car setMake(String make) {
        this.make = make;
        return this;
    }   
}

Есть ли какая-то особая причина, по которой встроенные библиотеки не работают таким образом? Есть ли недостаток в цепочке методов?

Возможно, я упустил что-то, что могло бы объяснить отсутствие цепочки методов, однако любой метод установки, возвращающий void по умолчанию, должен возвращать ссылку на this (по крайней мере, на мой взгляд, так и должно быть). Это сделало бы ситуации, подобные следующей, намного чище.

container.add((new JLabel("label text")).setMaximumSize(new Dimension(100,200)));

вместо более длинного: Примечание: это не помешает вам писать код таким образом, если вы захотите.

JLabel label = new JLabel("label text");
label.setMaximumSize(new Dimension(100,200));
container.add(label);

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


person Jon Taylor    schedule 19.07.2012    source источник
comment
Ваш пример также показывает недостатки цепочки методов: просто взглянув на код, не сразу видно, для чего вы устанавливаете максимальный размер: это метка или контейнер? Одна неуместная закрывающая скобка, и все может пойти совсем не так.   -  person biziclop    schedule 19.07.2012
comment
Да, я это понимаю, но использование пробелов определенно может помочь решить эту проблему.   -  person Jon Taylor    schedule 19.07.2012
comment
@JonTaylor Я не уверен, что использование пробелов/форматирования является хорошим решением.   -  person Colin D    schedule 19.07.2012
comment
@ColinD Если это просто проблема с читабельностью, то я не вижу причин не использовать пробелы, чтобы сделать ее более понятной, разве не по этой причине люди не пишут if/else все в одной строке или действительно не пишут всю свою программу на одной линии?   -  person Jon Taylor    schedule 19.07.2012
comment
@ColinD, ты неправильно процитировал меня, я добавил не буквально через 20 секунд после того, как оставил комментарий.   -  person Jon Taylor    schedule 19.07.2012
comment
@JonTaylor, твой отредактированный комментарий никогда не появлялся для меня, извини. Обычно я использую пробелы только для разделения различных фрагментов логики (новые строки/пустые строки) и для обозначения области видимости (табуляции/отступов). Я не уверен, как можно использовать пробелы для решения проблем, упомянутых бициклопом. Как бы ты?   -  person Colin D    schedule 19.07.2012
comment
@ColinD, например, если у метода много параметров, я часто помещаю каждый параметр в отдельную строку, точно так же вы можете поместить вызов каждого связанного метода в отдельную строку, для меня это сделало бы вещи намного яснее. Я написал пример на pastebin, так как не могу показать блоки кода в комментарии. pastebin.com/Njk3nV6M По крайней мере, мне кажется очевидным, что все методы вызываются для гамбургера, а не приказ.   -  person Jon Taylor    schedule 19.07.2012
comment
Я давно программировал Java, обязательны ли все скобки в однострочном фрагменте? В С# вы можете удалить те, что вокруг нового X()   -  person Guillaume86    schedule 19.07.2012
comment
@Guillaume86 Guillaume86 Если честно, я не совсем уверен, я всегда их вставлял, но у меня такое чувство, что в С# это может не понадобиться.   -  person Jon Taylor    schedule 19.07.2012
comment
@GeorgeStocker Я не понимаю, почему это должно быть закрыто, это совершенно правильный, хорошо сформулированный вопрос, на который я получил несколько совершенно правильных ответов. Это не должно было быть закрыто.   -  person Jon Taylor    schedule 20.07.2012
comment
@JonTaylor Я согласен, любой, кто хотя бы бегло взглянул на вопрос и ответы, может увидеть, что они содержат в основном фактическую информацию, которая может быть полезна и другим.   -  person biziclop    schedule 24.07.2012


Ответы (6)


Эх. Есть аргументы в пользу читабельности в обоих направлениях — есть такая вещь, как попытка поместить слишком много в одну строку.

Но, честно говоря, я подозреваю, что это связано с историческими причинами: широко распространенное «цепное» поведение не стало популярным или широко известным, когда, например. Свинг развивался. Вы можете возразить, что его следовало добавить позже, но подобные вещи, как правило, создают бинарную несовместимость и другие проблемы, к которым Sun/Oracle исторически относились крайне осторожно.

Более свежие библиотеки JDK - см., например. ByteBuffer для основного, хорошо известного примера -- < em>предоставили поведение цепочки и тому подобное, где это имеет смысл.

person Louis Wasserman    schedule 19.07.2012
comment
Да, если есть проблема с читабельностью. Да, жаль, что Swing не обновляют, но Swing всегда был одним из моих наименее любимых аспектов Java. - person Jon Taylor; 19.07.2012
comment
Я считаю, что это правильный ответ. Историческая справка: многие отцы-основатели Java имели опыт работы со Smalltalk. В Smalltalk не так много методов, а затем возвращаются, потому что у него есть синтаксическая конструкция для цепочки методов в форме точки с запятой. Возможно, это повлияло на разработчиков Java. Несмотря на то, что они не включали оператора цепочки. - person Tom Anderson; 19.07.2012
comment
@JonTaylor, жаль, что они не обновляют свинг: вроде как обновляют, это называется Java Fx 2. - person assylias; 19.07.2012
comment
Как я описал в своем ответе, цепочка была бы даже непрактична или даже невозможна в более ранних версиях Java. То, что они начали добавлять цепочку методов в новые классы, — не просто дань моде. - person biziclop; 19.07.2012
comment
Не мог бы анонимный downvoter объяснить? - person Louis Wasserman; 19.07.2012
comment
Я бы добавил, что отладка является еще одной важной причиной. - person razpeitia; 19.07.2012
comment
... Это причина отрицательного голоса? (Я не уверен, что даже принимаю это как причину — у меня не было особых проблем с отладкой беглого синтаксиса. Это может быть недостатком некоторых отладчиков, что отладка беглого синтаксиса сложна, но я бы не сказал, что это недостаток самого синтаксиса.) - person Louis Wasserman; 19.07.2012
comment
Я бы не сказал, что этот паттерн к тому времени не стал популярным, так как его использует даже 1.0 класс StringBuffer. Но знания Swing-разработчиков к тому времени — это другое дело… - person Holger; 04.11.2015

Еще одна причина, которую я могу придумать, — это производительность, или, точнее, не платить за то, чем вы не пользуетесь. return this после каждого метода не очень затратно, но все же требует нескольких дополнительных циклов ЦП и одного реестра ЦП.

Была даже идея добавить неявный return this в каждый метод, объявляющий возвращаемое значение void, но от нее отказались.

person Tomasz Nurkiewicz    schedule 19.07.2012
comment
Ах, хорошо, да, я думал, что неявный возврат this будет очень полезен для всего, что возвращает void, однако я вижу, как выполнение этого при каждом вызове метода потенциально может стать очень дорогостоящим с точки зрения производительности. - person Jon Taylor; 19.07.2012
comment
Разве JIT серьезно не оптимизирует это? По крайней мере, для каждого метода, достаточно короткого, чтобы его можно было встроить, он должен... - person Louis Wasserman; 19.07.2012
comment
@LouisWasserman: +1, но это похоже на большую работу. JIT должен будет проверить все вызовы метода и убедиться, что ни один из них на самом деле не использует возвращаемое значение. Этот процесс проверки должен быть повторен после загрузки каждого класса. Также помните о рефлексивных вызовах. - person Tomasz Nurkiewicz; 19.07.2012
comment
Пожалуйста, объясните, о, анонимный downvoter. - person Tomasz Nurkiewicz; 19.07.2012
comment
Что касается неявного return this, то его можно реализовать как чисто синтаксическую конструкцию — например, автоматическую перезапись с x.setFoo(1).setBar(2) на {<type> tmp = x; tmp.setFoo(1); tmp.setBar(2);}. (Я не минусующий.) - person gustafc; 19.07.2012
comment
@TomaszNurkiewicz JIT может скомпилировать две отдельные версии: одну с return this и одну без. Затем вызывающий может решить, какой из них вызывать, в зависимости от того, использует ли он возвращаемое значение. - person biziclop; 19.07.2012
comment
@gustafc: Это не может быть чисто синтаксическим; вы можете сделать это только с помощью методов, которые сами возвращают void. Эх. Я ожидаю, что JIT оптимизирует его в большинстве случаев. (В частности, я заметил, что ByteBuffer, который кажется довольно тщательно разработанным для высокопроизводительной работы, использует синтаксис цепочки...) - person Louis Wasserman; 19.07.2012
comment
@LouisWasserman Да, я имел в виду Была даже идея добавить неявный return this к каждому методу, объявляющему возвращаемое значение void [...] Может быть, синтаксис - неправильное слово, время компиляции лучше. Я имел в виду, что значением выражения <expr>.y(), где y является метод void, будет значение <expr>. Другими словами, разыменование значения void заставит компилятор просмотреть цепочку разыменования до тех пор, пока он не найдет выражение, отличное от void. Таким образом, это не будет иметь никаких последствий для производительности (возможно, кроме компиляции) и не будет видно в байтовом коде. - person gustafc; 19.07.2012
comment
Вкратце: компилятор позволил бы нам использовать метод void, как если бы он возвращал this, хотя на самом деле это не так. (Ему не нужно возвращать this, поскольку мы уже знаем в месте вызова, что он считает this.) - person gustafc; 19.07.2012
comment
Это ужасная причина, чтобы отказаться от потенциального дизайнерского решения, подобного этому. - person BlueRaja - Danny Pflughoeft; 19.07.2012
comment
Я не считаю, что производительность является причиной полностью отказаться от этого подхода. Если вы проанализируете свою программу и обнаружите, что цепочка является настоящим, истинным узким местом, то будьте уверены: не используйте ее. Но если вы этого не сделали, то заявление о причинах производительности является преждевременной оптимизацией. - person thedayturns; 20.07.2012
comment
@thedayturns зависит от того, если бы это была проблема с производительностью для всех методов возврата void, независимо от того, закончите ли вы цепочку или нет, у программиста не было бы другого выбора, кроме как принять любые связанные с этим проблемы с производительностью. Я не претендую на понимание того, как на самом деле работает компилятор, и хотя я хотел бы узнать больше (и делать это каждый день), я не в состоянии сказать, можно ли это реализовать таким образом, что если вы выберете чтобы не использовать его, его можно было бы каким-то образом оптимизировать, чтобы он действовал так же, как пустота. - person Jon Taylor; 20.07.2012
comment
Если бы он был встроен в коды операций, ему не пришлось бы каждый раз использовать полную инструкцию jrs; скорее, компилятор будет просто смотреть везде, где вы действительно используете цепочку методов, и помещать в стек дополнительную ссылку на это. Вместо того, чтобы всегда добавлять оператор возврата, он просто добавлял бы стек, где это необходимо. - person Ajax; 01.02.2013
comment
Трудно поверить, что он используется не из соображений производительности, когда основные классы, такие как StringBuffer (начиная с версии 1.0), используют его в большинстве методов… - person Holger; 04.11.2015

Хотя мы можем только догадываться об истинной причине, одна из них может заключаться в том, что, строго говоря, это не очень ОО.

Если вы рассматриваете методы как действия, представляющие действие смоделированного мира, цепочка методов не вписывается в эту картину.

Другой причиной может быть хаос, который может возникнуть, когда вы пытаетесь переопределить связанный метод. Представьте себе такую ​​ситуацию:

class Foo {
   Foo chainedMethod() {...}
}

class Bar extends Foo {
   Foo chainedMethod() {...} //had to return Foo for Java versions < 1.5
   void doStuff();
}

Поэтому, когда вы пытаетесь сделать new Bar().chainedMethod().doStuff(), он не скомпилируется. И ((Bar)new Bar().chainedMethod()).doStuff() выглядит не очень хорошо, не так ли :)

person biziclop    schedule 19.07.2012
comment
Я не уверен, что согласен, я бы сказал, что вы просто выполняете несколько действий одновременно. - person Jon Taylor; 19.07.2012
comment
@JonTaylor Но почему метод возвращает значение, если в моделируемом мире этого не происходит? - person biziclop; 19.07.2012
comment
Я понимаю, к чему вы пришли, но я вижу, что это похоже на запрос бургера с добавлением сыра и кетчупа, а не бургера с добавлением сыра и того же бургера снова с добавлением кетчупа . Да, мой пример глупый, но я думаю, что он передает мою точку зрения (возможно). - person Jon Taylor; 19.07.2012
comment
Но +1 за ваш пример. - person Jon Taylor; 19.07.2012
comment
@JonTaylor Решением OO было бы комбинированное действие setCheeseAndKetchup(). Должен сказать, что я не обязательно согласен с этой точкой зрения, но жесткое ООП основано на принципе методов, строго моделирующих реальную деятельность. - person biziclop; 19.07.2012
comment
Да, хороший момент. Должен сказать, что я несколько не согласен с этой точкой зрения. Я имею в виду, что я бы не стал объединять их в setCheeseAndKetchup(), по крайней мере, не в более сложном примере, но тогда, я думаю, вы могли бы использовать метод setToppings либо с набором логических параметров, либо с некоторым вводом битовой маски, чтобы разрешить выбор начинки. - person Jon Taylor; 19.07.2012
comment
Я и не говорю, что согласен с этим. Я предпочитаю практические решения, и каждая теория, которая говорит, что вы должны и не должны делать, может быть расширена за пределы практического применения. Слишком строгая интерпретация ООП может быть настоящей болью, но попытка сделать все с цепочкой методов и без капли здравого смысла может быть такой же болезненной. - person biziclop; 19.07.2012
comment
Я категорически не согласен с тем, что цепочка методов/свободный интерфейс не являются объектно-ориентированными. Просто потому, что чем-то можно злоупотреблять c.f. Getter/Setters не делает объектно-ориентированных программ. - person Martin Spamer; 02.05.2013
comment
@MartinSpamer Вы можете не соглашаться, но вы ошибаетесь. Проблема не в злоупотреблении, а в том, что оно концептуально несовместимо с тем, что метод должен представлять в ООП. - person biziclop; 09.05.2013
comment
Тогда у вас не возникнет проблем с определением того, какой принцип объектно-ориентированного программирования он нарушает. - person Martin Spamer; 22.07.2013
comment
@MartinSpamer Принцип, согласно которому объекты должны моделировать объекты реального мира, а методы должны соответствовать действиям, выполняемым над указанными объектами. Метод setXXX(), возвращающий объект, для которого он был вызван, не имеет реального смысла, это хак. Невероятно полезный хак, ради которого стоит отказаться от строгой интерпретации ОО, но тем не менее хак. - person biziclop; 22.07.2013

Цепочка методов обычно используется с шаблоном Builder, и вы можете увидеть это в классе StringBuilder. Помимо этого, цепочка методов может сделать разделение команд и создания менее четким, например, должен ли repaint() также быть частью свободного интерфейса? Тогда я мог бы:

container.add(label).repaint().setMaximumSize(new Dimension(100,200)));

и вдруг порядок моих вызовов становится важным, что может быть скрыто в цепочке методов.

person Garrett Hall    schedule 19.07.2012
comment
Да, я понимаю, как такие вещи могут сбивать с толку, но если кто-то предлагает возможность что-то сделать, это не значит, что вы всегда должны это делать. Для меня добавление прослушивателей событий — отличный пример использования цепочек, в то время как описанные выше ситуации, такие как вызов перерисовки, могут сбить с толку. Но тогда вы могли бы сказать то же самое о таких вещах, как троичные операторы, если они используются друг в друге, некоторые люди находят их ужасными, в то время как другие, такие как я, предпочитают их стандартным операторам if (в зависимости от сценария). - person Jon Taylor; 19.07.2012
comment
Мне действительно нравится использовать цепочка методов (также известная как свободный интерфейс), хотя я понимаю, что не следует заходить слишком далеко. - person Garrett Hall; 19.07.2012
comment
Да, определенно, я думаю, что это может быть очень полезно, но я понимаю, что в определенных ситуациях это может сбивать с толку, но, как я уже сказал, только потому, что есть возможность сделать это, не означает, что люди должны использовать это в каждой ситуации. - person Jon Taylor; 20.07.2012

Причина, по которой не реализована цепочка методов, может заключаться в следующем:

  • Самая большая проблема для цепочки методов — проблема завершения. Хотя есть обходные пути, обычно, если вы сталкиваетесь с этим, вам лучше использовать вложенную функцию. Вложенные функции также являются лучшим выбором, если у вас возникают проблемы с контекстными переменными.

  • Нет никакой гарантии, что объект вне цепочки действительно вернет допустимый, ненулевой объект. Кроме того, отладка этого стиля кода часто намного сложнее, поскольку многие *IDE* не оценивают вызов метода во время отладки как объект, который вы можете проверить.

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

  • Существует также проблема с производительностью, которая будет сильно влиять на память.

person GingerHead    schedule 19.07.2012
comment
Не хотел бы анонимный голосующий за понижение объяснить причину своего понижения? - person GingerHead; 19.07.2012

person    schedule
comment
Если я неправильно понял то, что вы говорите, я не думаю, что говорю о Законе Деметры. Я не говорю о вызове методов для объектов внутри объектов, я просто связываю вызовы методов с одним объектом. Действительно ли применим к этому Закон Деметры? - person Jon Taylor; 19.07.2012
comment
Это зависит! Я интерпретировал ваш первоначальный вопрос, возможно, более широко, чем вы предполагали, но это, безусловно, может быть применимо. - person Jool; 19.07.2012
comment
afaik, предметом здесь является один объект, для которого у вас есть много настроек, и вы хотите использовать o.settitle(x).setauthor(y).setdate(z) скорее чем o.settitle(x); о.установитьавтор(у); о.setdate(z); - person PypeBros; 19.07.2012
comment
-1 Это не имеет никакого отношения к Закону Деметры (вы всегда работаете с одним и тем же экземпляром объекта). - person sleske; 20.07.2012
comment
@sleske, хотя я согласен с вами в контексте этого вопроса, я вижу, откуда он взялся. Причина в том, что некоторые классы все еще используют форму цепочки (выглядит так же), но они не обязательно возвращают объекты, о которых вы думаете. На самом деле я не могу вспомнить ни одного примера в это раннее утро, но, хотя они не являются типичной цепочкой методов, упомянутой в вопросе, они несколько похожи. - person Jon Taylor; 20.07.2012
comment
Цепочка методов с делегированием — очень хороший способ поговорить с детьми вашего друга в соответствии с Законом Деметры. - person Martin Spamer; 02.05.2013