Цепочка методов - почему это хорошая практика или нет?

Цепочка методов - это практика, когда методы объекта возвращают сам объект, чтобы результат был вызван для другой метод. Нравится:

participant.addSchedule(events[1]).addSchedule(events[2]).setStatus('attending').save()

Это кажется хорошей практикой, поскольку она дает читаемый код или «свободный интерфейс». Однако мне кажется, что вместо этого он нарушает нотацию вызова объекта, подразумеваемую самой объектной ориентацией - результирующий код не представляет выполнение действий по отношению к результату предыдущего метода, каковым является объектно-ориентированный код. обычно ожидается, что он будет работать:

participant.getSchedule('monday').saveTo('monnday.file')

Это различие создает два разных значения для точечной нотации «вызова результирующего объекта»: в контексте цепочки приведенный выше пример будет читаться как сохранение объекта участник, даже если пример фактически предназначен для сохранения объекта расписания, полученного getSchedule.

Я понимаю, что разница здесь в том, следует ли ожидать, что вызываемый метод что-то вернет или нет (в этом случае он вернет сам вызываемый объект для связывания). Но эти два случая неотличимы от самой нотации, только от семантики вызываемых методов. Когда цепочка методов не используется, я всегда могу знать, что вызов метода работает с чем-то связанным с результатом предыдущего вызова - с цепочкой это предположение нарушается, и мне приходится семантически обрабатывать всю цепочку чтобы понять, что на самом деле представляет собой вызываемый объект. Например:

participant.attend(event).setNotifications('silent').getSocialStream('twitter').postStatus('Joining '+event.name).follow(event.getSocialId('twitter'))

Здесь два последних вызова метода относятся к результату getSocialStream, а предыдущие - к участнику. Возможно, это плохая практика - на самом деле писать цепочки, в которых меняется контекст (не так ли?), Но даже тогда вам придется постоянно проверять, действительно ли точечные цепочки, которые выглядят похожими, находятся в одном контексте или работают только с результатом. .

Мне кажется, что, хотя на первый взгляд цепочка методов создает читаемый код, перегрузка значения точечной нотации приводит только к еще большей путанице. Поскольку я не считаю себя гуру программирования, я предполагаю, что это моя вина. Итак: что мне не хватает? Я как-то неправильно понимаю цепочку методов? Есть ли случаи, когда цепочка методов особенно хороша, а в каких - особенно плохо?

Примечание: я понимаю, что этот вопрос может быть истолкован как изложение мнения, замаскированного под вопрос. Однако это не так - я искренне хочу понять, почему цепочка считается хорошей практикой и где я ошибаюсь, полагая, что она нарушает присущую объектно-ориентированной нотации.


person Ilari Kajaste    schedule 09.07.2009    source источник
comment
Кажется, что цепочка методов - это по крайней мере один из способов написания умного кода на java. Даже если не все согласны ..   -  person Mercer Traieste    schedule 09.07.2009
comment
Они также называют эти методы плавным или плавным интерфейсом. Возможно, вы захотите обновить свой заголовок, чтобы использовать этот термин.   -  person S.Lott    schedule 09.07.2009
comment
В другом обсуждении SO было сказано, что свободный интерфейс - это более широкая концепция, связанная с читабельностью кода, и цепочка методов - только один из способов добиться этого. Однако они тесно связаны между собой, поэтому я добавил теги и ссылки на текучие интерфейсы в тексте - я думаю, что этого достаточно.   -  person Ilari Kajaste    schedule 09.07.2009
comment
Я пришел к выводу, что цепочка методов - это, по сути, обходной путь, позволяющий добавить недостающую функцию в синтаксис языка. В этом действительно не было бы необходимости, если бы был встроенная альтернативная нотация ., которая игнорирует любые возвращаемые mehtod значения и всегда вызывает любые связанные методы, использующие один и тот же объект.   -  person Ilari Kajaste    schedule 07.07.2011
comment
Это отличная идиома программирования, но, как и все отличные инструменты, ею злоупотребляют.   -  person Martin Spamer    schedule 02.05.2013
comment
Свободный синтаксис Microsoft LINQ демонстрирует мощь объединения методов, доступного в качестве фундаментального компонента .NET. Фреймворк. Также см. Стоит ли использовать цепочку методов в C #?.   -  person DavidRR    schedule 17.11.2014
comment
Другой очень известный пример объединения методов - это библиотека JavaScript jQuery.   -  person DavidRR    schedule 17.11.2014
comment
Я не согласен с этой предпосылкой: результирующий код не отражает выполнение действий с результатом предыдущего метода. Конечно, есть. Результат participant.AddSchedule(events[1]) - участник, которому только что были добавлены события [1] в свое расписание.   -  person Adrian McCarthy    schedule 15.11.2017
comment
Laravel Eloquent - лучший пример приложения для объединения методов   -  person Elbo Shindi Pangestu    schedule 06.05.2020


Ответы (18)


Согласен, это субъективно. По большей части я избегаю связывания методов, но недавно я также обнаружил случай, когда это было правильным решением - у меня был метод, который принимал что-то вроде 10 параметров и нуждался в большем количестве, но в большинстве случаев вам нужно было только указать немного. С переопределением это очень быстро стало очень громоздким. Вместо этого я выбрал цепочный подход:

MyObject.Start()
    .SpecifySomeParameter(asdasd)
    .SpecifySomeOtherParameter(asdasd)
    .Execute();

Подход с использованием цепочки методов был необязательным, но он упростил написание кода (особенно с IntelliSense). Имейте в виду, что это единичный случай, который не является общей практикой в ​​моем коде.

Дело в том, что в 99% случаев вы, вероятно, можете обойтись так же или даже лучше без связывания методов. Но есть 1%, где это лучший подход.

person Vilx-    schedule 09.07.2009
comment
IMO, лучший способ использовать цепочку методов в этом сценарии - создать объект параметра, который будет передан функции, например P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);. У вас есть преимущество в том, что вы можете повторно использовать этот объект параметра в других вызовах, что является плюсом! - person pedromanoel; 12.06.2012
comment
Только мой цент вклада в шаблоны, фабричный метод обычно имеет только одну точку создания, а продукты - это статический выбор, основанный на параметрах фабричного метода. Создание цепочки больше похоже на шаблон Builder, где вы вызываете разные методы для получения результата, методы могут быть необязательными, как в цепочке методов, у нас может быть что-то вроде PizzaBuilder.AddSauce().AddDough().AddTopping() дополнительных ссылок здесь - person Marco Medrano; 15.03.2013
comment
Цепочка методов (как показано в исходном вопросе) считается плохой, если она нарушает закон Деметры. См .: ifaceoughtts.net/2006 / 03/07 / Ответ, данный здесь, фактически следует этому закону, поскольку он является строительным шаблоном. - person Angel O'Sphere; 30.04.2014
comment
@Marco Medrano Пример PizzaBuilder всегда меня беспокоил с тех пор, как я прочитал его в JavaWorld много лет назад. Я чувствую, что должен добавлять соус в пиццу, а не в повар. - person Breandán Dalton; 28.02.2017
comment
@ B.Dalton - Подумайте об этом так - это PizzaBuilder делает добавление, а не то, что что-то добавляется в PizzaBuilder. В конце концов, PizzaBuilder произведет пиццу. Но пицца не может добавить к себе добавку. :) - person Vilx-; 28.02.2017
comment
Я знаю, что ты имеешь в виду, Вилкс. Но все же, когда я прочитал list.add(someItem), я прочитал это, поскольку этот код добавляет someItem к объекту list. поэтому, когда я читаю PizzaBuilder.AddSauce(), я читаю это естественно, поскольку этот код добавляет соус к объекту PizzaBuilder. Другими словами, я вижу оркестратор (тот, который выполняет добавление) как метод, в который встроен код list.add(someItem) или PizzaBuilder.addSauce(). Но я думаю, что пример PizzaBuilder немного искусственен. Ваш пример MyObject.SpecifySomeParameter(asdasd) работает нормально для меня. - person Breandán Dalton; 28.02.2017
comment
@ Б.Дальтон - Хорошо, справедливо. Ну, как я уже сказал, это субъективно. У кого-то это работает, у кого-то сложнее. - person Vilx-; 28.02.2017
comment
@ Б.Дальтон Я понял, о чем ты. Представьте, что есть Директор (начальник, заказчик), управляющий PizzaBuilder. Эй, строитель, добавь это в мою пиццу, вот это и это. Наконец, Директор выполнит последнюю команду GetResult() или отдаст мне пиццу, которую вы построили для меня. Таким образом, PizzaBuilder не добавляет себе соус, который он добавляет к базовому продукту, который в конце будет полностью построен и доставлен Директору. - person Marco Medrano; 01.03.2017
comment
@ Марко Медрано Да, Марко, именно так. И AddXXX - это то, что меня беспокоит. Для меня это уже имеет другое семантическое значение (т.е. добавить XXX к объекту слева от точки). Я думал о том, как это улучшить, и я бы изменил AddSauce и т. Д. На UsingSauce и т. Д. (Или WithXXX), чтобы он читался более естественно (для меня) как PizzaBuilder.UsingDough().UsingSauce().UsingTopping().MakePizza(); - person Breandán Dalton; 01.03.2017
comment
@ B.Dalton: да, ваше решение читабельно, если команде это нравится, мне это имеет смысл! - person Marco Medrano; 03.03.2017
comment
Существует несубъективная сторона цепочки методов, не имеющая ничего общего с предпочтениями стиля: только Fluent может работать , если все методы в цепочке никогда не возвращают null. Во многих проектах, которые мне приходится проверять / исправлять, цепочка методов с использованием методов, которые могут возвращать значение null, является основной причиной трудных для точного определения дефектов NullPointerException. - person Darrell Teague; 11.08.2017
comment
но почему они не могут быть просто отдельными звонками? MyObject.Start() MyObject.SpecifySomeParameter(asdasd) MyObject.SpecifySomeOtherParameter(asdasd) MyObject.Execute();? Что делает такую ​​выгодную или необходимую цепочку? - person ahnbizcad; 22.10.2020
comment
@ahnbizcad - Конечно, ты тоже можешь это сделать. Мне просто нравится, как выглядит этот код. Полагаю, это своего рода ... личное предпочтение стиля? Но в конечном результате разницы нет. - person Vilx-; 22.10.2020

Только мои 2 цента;

Цепочка методов усложняет отладку: - Вы не можете поместить точку останова в краткую точку, чтобы вы могли приостановить программу именно там, где хотите - Если один из этих методов вызывает исключение, и вы получаете номер строки, вы понятия не имеете какой метод в «цепочке» вызвал проблему.

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

РЕДАКТИРОВАТЬ: в комментарии упоминается, что цепочка методов и разрыв строки разделены. Это правда. Однако, в зависимости от отладчика, может или не может быть возможно разместить точку останова в середине оператора. Даже если вы можете, использование отдельных строк с промежуточными переменными дает вам гораздо больше гибкости и целый набор значений, которые вы можете изучить в окне Watch, что помогает процессу отладки.

person RAY    schedule 28.10.2010
comment
Разве использование новой строки и цепочки методов не является независимым? Вы можете связать каждый вызов с новой строкой в ​​соответствии с @ Vilx- answer, и вы обычно можете поместить несколько отдельных операторов в одну строку (например, используя точку с запятой в Java). - person brabster; 13.06.2013
comment
Если все классы, использующие цепочку, реализуют интерфейс, такой как interface Chainable { Chainable chainID(Object o_cid); Object getChainID(); }, то очень легко определить, где произошла ошибка. Однако это должно быть заложено в основу вашего кода. - person aliteralmind; 01.01.2014
comment
Этот ответ полностью оправдан, но он просто показывает слабость, присущую всем отладчикам, которые я знаю, и не имеет прямого отношения к вопросу. - person masterxilo; 09.05.2014
comment
@Brabster. Они могут быть отдельными для определенных отладчиков, однако наличие отдельных вызовов с промежуточными переменными по-прежнему дает вам гораздо больший объем информации, пока вы исследуете ошибки. - person RAY; 08.09.2014
comment
+1, вы никогда не знаете, что возвращал каждый метод при пошаговой отладке цепочки методов. Шаблон цепочки методов - это хитрость. Это худший кошмар для программиста по техническому обслуживанию. - person Lee Kowalkowski; 04.03.2015
comment
Я тоже не большой поклонник создания цепочек, но почему бы просто не поставить точку останова в определениях методов? - person Pankaj Sharma; 31.05.2018
comment
Это единственно правильный ответ, и он должен быть принятым. Большинство других ответов здесь, в этой теме, касаются только процесса написания кода. При работе с программным обеспечением необходимо учитывать весь жизненный цикл продукта. Компания будет тратить больше времени на поддержание кода в течение всего срока службы продукта, чем на собственное написание кода. - person Shane; 14.03.2019

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

foo.setHeight(100).setWidth(50).setColor('#ffffff');
foo.moveTo(100,100).highlight();

Я не использую его, когда в моем примере один или несколько связанных методов возвращают любой объект, кроме foo. Хотя синтаксически вы можете связать что угодно, пока вы используете правильный API для этого объекта в цепочке, изменение объектов IMHO делает вещи менее читаемыми и может действительно сбивать с толку, если API для разных объектов имеют какое-либо сходство. Если в конце вы вызовете какой-нибудь действительно распространенный метод (.toString(), .print() и т. Д.), На какой объект вы в конечном итоге воздействуете? Кто-то, случайно читающий код, может не уловить, что это будет неявно возвращенный объект в цепочке, а не исходная ссылка.

Объединение различных объектов в цепочку также может привести к неожиданным нулевым ошибкам. В моих примерах, предполагая, что foo действителен, все вызовы методов являются «безопасными» (например, действительны для foo). В примере OP:

participant.getSchedule('monday').saveTo('monnday.file')

... нет никакой гарантии (как сторонний разработчик, смотрящий на код), что getSchedule действительно вернет действительный, ненулевой объект расписания. Кроме того, отладка этого стиля кода часто бывает намного сложнее, поскольку многие IDE не будут оценивать вызов метода во время отладки как объект, который вы можете проверить. ИМО, в любое время, когда вам может понадобиться объект для проверки для целей отладки, я предпочитаю иметь его в явной переменной.

person Brian Moeskau    schedule 10.07.2009
comment
Если есть вероятность, что Participant не имеет действительного Schedule, тогда метод getSchedule предназначен для возврата типа Maybe(of Schedule), а метод saveTo предназначен для приема типа Maybe. - person Lightman; 25.12.2015

У Мартина Фаулера есть хорошее обсуждение:

Сочетание методов

Когда это использовать

Цепочка методов может значительно улучшить читаемость внутреннего DSL и в результате в некоторых умах стала почти синонимом для внутренних DSL. Однако объединение методов лучше всего использовать вместе с другими комбинациями функций.

Цепочка методов особенно эффективна с такими грамматиками, как parent :: = (this | that) *. Использование разных методов обеспечивает удобочитаемый способ увидеть, какой аргумент будет следующим. Точно так же необязательные аргументы можно легко пропустить с помощью цепочки методов. Список обязательных предложений, таких как parent :: = first second, не так хорошо работает с базовой формой, хотя его можно хорошо поддерживать с помощью прогрессивных интерфейсов. В большинстве случаев в этом случае я бы предпочел вложенную функцию.

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

person Dirk Vollmar    schedule 09.07.2009
comment
Что вы имеете в виду под DSL? Специфический для домена язык - person Sören; 19.12.2019
comment
@ Sören: Фаулер обращается к предметно-ориентированным языкам. - person Dirk Vollmar; 19.12.2019

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

Как:

someList.addObject("str1").addObject("str2").addObject("str3")

лучше, чем:

someList.addObject("str1")
someList.addObject("str2")
someList.addObject("str3")

Исключение может быть, когда addObject () возвращает новый объект, и в этом случае несвязанный код может быть немного более громоздким, например:

someList = someList.addObject("str1")
someList = someList.addObject("str2")
someList = someList.addObject("str3")

Изменить: мое мнение по этому поводу изменилось за последние 10 лет. Для изменяемых объектов я по-прежнему не вижу большой пользы, хотя это полезно для избежания небольшого дублирования. Но теперь, когда я намного больше отдаю предпочтение неизменности, цепочка методов - мой предпочтительный способ выполнения неразрушающих обновлений, который я использую все время.

person Tom Dalling    schedule 09.07.2009
comment
Это более кратко, поскольку вы избегаете двух частей someList даже в своем первом примере и получаете одну строку вместо трех. Хорошо это или плохо, зависит от разных вещей и, возможно, зависит от вкуса. - person Fabian Steeg; 10.07.2009
comment
Настоящее преимущество использования someList только один раз в том, что в этом случае гораздо проще дать ему более длинное и описательное имя. Каждый раз, когда имя должно появиться несколько раз в быстрой последовательности, есть тенденция делать его коротким (чтобы уменьшить количество повторений и улучшить читаемость), что делает его менее описательным и ухудшает читаемость. - person Chris Dodd; 28.10.2010
comment
Хорошее замечание относительно удобочитаемости и принципов DRY с оговоркой, что цепочка методов предотвращает самоанализ возвращаемого значения данного метода и / или подразумевает предположение о пустых списках / наборах и ненулевых значениях из каждого метода в цепочке (ошибка, вызывающая многие NPE в системах мне приходится часто отлаживать / исправлять). - person Darrell Teague; 11.08.2017
comment
@ChrisDodd Никогда не слышал об автозавершении? - person inf3rno; 17.02.2019
comment
@ inf3rno: Как автозаполнение улучшает читаемость? Во всяком случае, это наоборот - позволяет легко писать длинные, нечитаемые беспорядки, которые может понять только компьютер. - person Chris Dodd; 17.02.2019
comment
@ChrisDodd Я имел в виду, что он сокращает повторение путем набора текста. Я не предпочитаю очень длинные имена переменных, потому что либо они являются признаком чрезмерной разработки, либо разработчик не понимает, что означают контекст и локальное значение. Думаю, это ухудшает читабельность, не делает их короче. Я не думаю, что повторение имени оказывает существенное влияние на читаемость, человеческий мозг очень хорошо распознает повторение текста, если он имеет одинаковый отступ. С другой стороны, цепочка методов делает возвращаемые значения неявными и пытается решить сложные проблемы одним оператором, что ухудшает читабельность. - person inf3rno; 17.02.2019
comment
человеческий мозг очень хорошо распознает повторение текста, если он имеет одинаковые отступы - это как раз проблема с повторением: оно заставляет мозг фокусироваться на повторяющемся шаблоне. Итак, чтобы прочитать И ПОНИМАТЬ код, вы должны заставить свой мозг смотреть за пределы повторяющегося паттерна / за ним, чтобы увидеть, что на самом деле происходит, а именно различия между повторяющимся паттерном, которые ваш мозг склонен замалчивать. Вот почему повторение так плохо для читабельности кода. - person Chris Dodd; 17.02.2019
comment
несмотря на inf3rnos, несколько ухмылка комментирует LoL. Я склоняюсь к нескольким рядам, а не к одному. Потому что эта одна строка может стать ужасно длинной (по правилу 80 знаков). Кроме того, не знаю, как вы, ребята, но одна строка на каждую строку хорошо разделена для моего мозга, и я сразу вижу ее три раза, в то время как длинная строка фактически заставляет мой мозг пропускать, а затем возвращаться и тщательно подсчитывать каждое появление, просто чтобы перестраивать. - person brat; 20.01.2021
comment
если нас беспокоит длина, то список один, но цепочка методов означает, что вам придется создать n много определений методов, и все это просто глупо простой установщик - это НЕ более лаконично, чем простой примитивный список - person ahnbizcad; 15.03.2021
comment
message = get_default_dict () message.update ({'alpha': 0.4}) send (message) vs send (get_default_dict (). update ({'alpha': 0.4})) (где мы делаем вид, что update возвращает обновленный словарь). Второй вариант может быть немного сложнее отлаживать с помощью операторов печати, но он имеет то преимущество, что он не загромождает пространство переменных и пространство редактора промежуточными выбрасываемыми переменными без глупости добавления его в отдельную функцию. - person Jacob Lee; 19.07.2021

Многие используют цепочку методов как форму удобства, а не думают о каких-либо проблемах с удобочитаемостью. Цепочка методов приемлема, если она включает выполнение одного и того же действия с одним и тем же объектом, но только если она действительно улучшает читаемость, а не только для написания меньшего количества кода.

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

person aberrant80    schedule 10.07.2009

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

Приведу пример:

foodStore - это объект, состоящий из множества ваших продуктовых магазинов. foodstore.getLocalStore () возвращает объект, содержащий информацию о ближайшем к параметру хранилище. getPriceforProduct (что угодно) - это метод этого объекта.

Итак, когда вы вызываете foodStore.getLocalStore (параметры) .getPriceforProduct (что угодно)

вы зависите не только от FoodStore, как вы, но и от LocalStore.

Если getPriceforProduct (что-либо) когда-либо изменится, вам нужно изменить не только FoodStore, но и класс, который вызвал связанный метод.

Вы всегда должны стремиться к слабой связи между классами.

При этом я лично люблю связывать их при программировании Ruby.

person rprandi    schedule 09.07.2009

Это кажется довольно субъективным.

Цепочка методов - это не что-то плохое или хорошее по своей сути.

Читаемость - это самое главное.

(Также учтите, что наличие большого количества связанных методов сделает вещи очень хрупкими, если что-то изменится)

person John Nicholas    schedule 09.07.2009
comment
Это действительно может быть субъективным, отсюда и субъективный признак. Я надеюсь, что ответы подчеркнут для меня, в каких случаях цепочка методов была бы хорошей идеей - прямо сейчас я не вижу ничего особенного, но я думаю, что это просто моя неспособность понять положительные стороны концепции, а не что-то изначально плохое в самой цепочке. - person Ilari Kajaste; 09.07.2009
comment
Разве это не было бы плохо, если бы это привело к высокому сцеплению? Разбивка цепочки на отдельные утверждения не снижает читабельность. - person aberrant80; 10.07.2009
comment
зависит от того, насколько категоричным вы хотите быть. Если это приведет к чему-то более читабельному, это может быть предпочтительнее во многих ситуациях. Большая проблема, с которой я сталкиваюсь с этим подходом, заключается в том, что большинство методов объекта будут возвращать ссылки на сам объект, но часто метод возвращает ссылку на дочерний объект, на котором вы можете связать больше методов. Как только вы начнете это делать, другому программисту станет очень трудно распутать то, что происходит. Кроме того, любое изменение в функциональности метода станет проблемой для отладки большого составного оператора. - person John Nicholas; 10.07.2009

Преимущества цепочки
т. е. там, где мне нравится ее использовать.

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

Я знаю, что это надуманный пример, но скажу, что у вас есть следующие классы

Public Class Location
   Private _x As Integer = 15
   Private _y As Integer = 421513

   Public Function X() As Integer
      Return _x
   End Function
   Public Function X(ByVal value As Integer) As Location
      _x = value
      Return Me
   End Function

   Public Function Y() As Integer
      Return _y
   End Function
   Public Function Y(ByVal value As Integer) As Location
      _y = value
      Return Me
   End Function

   Public Overrides Function toString() As String
      Return String.Format("{0},{1}", _x, _y)
   End Function
End Class

Public Class HomeLocation
   Inherits Location

   Public Overrides Function toString() As String
      Return String.Format("Home Is at: {0},{1}", X(), Y())
   End Function
End Class

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

  Dim loc As New HomeLocation()
  loc.X(1337)
  PrintLocation(loc)

Но разве это не легче читать:

  PrintLocation(New HomeLocation().X(1337))

Или как насчет ученика?

Public Class Dummy
   Private _locA As New Location()
   Public Sub New()
      _locA.X(1337)
   End Sub
End Class

vs

Public Class Dummy
   Private _locC As Location = New Location().X(1337)
End Class

Вот как я использую цепочку, и обычно мои методы предназначены только для настройки, поэтому они состоят всего из 2 строк, установите значение, затем Return Me. Для нас он убрал огромные строки, которые очень трудно читать и понять, в одну строку, которая читается как предложение. что-то вроде

New Dealer.CarPicker().Subaru.WRX.SixSpeed.TurboCharged.BlueExterior.GrayInterior.Leather.HeatedSeats

Против чего-то вроде

New Dealer.CarPicker(Dealer.CarPicker.Makes.Subaru
                   , Dealer.CarPicker.Models.WRX
                   , Dealer.CarPicker.Transmissions.SixSpeed
                   , Dealer.CarPicker.Engine.Options.TurboCharged
                   , Dealer.CarPicker.Exterior.Color.Blue
                   , Dealer.CarPicker.Interior.Color.Gray
                   , Dealer.CarPicker.Interior.Options.Leather
                   , Dealer.CarPicker.Interior.Seats.Heated)

Нарушение цепочки
т. е. там, где я не люблю его использовать.

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

Также существует опасение, что подпрограмма вернет недопустимые данные, до сих пор я использовал цепочку только тогда, когда возвращаю тот же вызываемый экземпляр. Как было указано, если вы объединяете классы в цепочку, вы усложняете отладку (какой из них вернул ноль?) И можете увеличить взаимосвязь зависимостей между классами.

Заключение

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

Я стараюсь соблюдать эти правила.

  1. Старайтесь не цепляться между классами
  2. Сделайте подпрограммы специально для цепочки
  3. Делайте только ОДНО в рутине цепочки
  4. Используйте его, когда он улучшает читаемость
  5. Используйте его, когда он упрощает код
person Apeiron    schedule 20.05.2011

Цепочка методов может позволить разрабатывать расширенные DSL непосредственно на Java. По сути, вы можете смоделировать как минимум следующие типы правил DSL:

1. SINGLE-WORD
2. PARAMETERISED-WORD parameter
3. WORD1 [ OPTIONAL-WORD]
4. WORD2 { WORD-CHOICE-A | WORD-CHOICE-B }
5. WORD3 [ , WORD3 ... ]

Эти правила могут быть реализованы с использованием этих интерфейсов.

// Initial interface, entry point of the DSL
interface Start {
  End singleWord();
  End parameterisedWord(String parameter);
  Intermediate1 word1();
  Intermediate2 word2();
  Intermediate3 word3();
}

// Terminating interface, might also contain methods like execute();
interface End {}

// Intermediate DSL "step" extending the interface that is returned
// by optionalWord(), to make that method "optional"
interface Intermediate1 extends End {
  End optionalWord();
}

// Intermediate DSL "step" providing several choices (similar to Start)
interface Intermediate2 {
  End wordChoiceA();
  End wordChoiceB();
}

// Intermediate interface returning itself on word3(), in order to allow for
// repetitions. Repetitions can be ended any time because this interface
// extends End
interface Intermediate3 extends End {
  Intermediate3 word3();
}

С помощью этих простых правил вы можете реализовать сложные DSL, такие как SQL, непосредственно в Java, как это делает jOOQ, библиотека. что я создал. См. Довольно сложный пример SQL, взятый из мой блог здесь:

create().select(
    r1.ROUTINE_NAME,
    r1.SPECIFIC_NAME,
    decode()
        .when(exists(create()
            .selectOne()
            .from(PARAMETERS)
            .where(PARAMETERS.SPECIFIC_SCHEMA.equal(r1.SPECIFIC_SCHEMA))
            .and(PARAMETERS.SPECIFIC_NAME.equal(r1.SPECIFIC_NAME))
            .and(upper(PARAMETERS.PARAMETER_MODE).notEqual("IN"))),
                val("void"))
        .otherwise(r1.DATA_TYPE).as("data_type"),
    r1.NUMERIC_PRECISION,
    r1.NUMERIC_SCALE,
    r1.TYPE_UDT_NAME,
    decode().when(
    exists(
        create().selectOne()
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.notEqual(r1.SPECIFIC_NAME))),
        create().select(count())
            .from(r2)
            .where(r2.ROUTINE_SCHEMA.equal(getSchemaName()))
            .and(r2.ROUTINE_NAME.equal(r1.ROUTINE_NAME))
            .and(r2.SPECIFIC_NAME.lessOrEqual(r1.SPECIFIC_NAME)).asField())
    .as("overload"))
.from(r1)
.where(r1.ROUTINE_SCHEMA.equal(getSchemaName()))
.orderBy(r1.ROUTINE_NAME.asc())
.fetch()

Еще один хороший пример - jRTF, небольшой DSL, предназначенный для создания документов RTF непосредственно в Java. Пример:

rtf()
  .header(
    color( 0xff, 0, 0 ).at( 0 ),
    color( 0, 0xff, 0 ).at( 1 ),
    color( 0, 0, 0xff ).at( 2 ),
    font( "Calibri" ).at( 0 ) )
  .section(
        p( font( 1, "Second paragraph" ) ),
        p( color( 1, "green" ) )
  )
).out( out );
person Lukas Eder    schedule 05.01.2012
comment
@ user877329: Да, его можно использовать практически на любом объектно-ориентированном языке программирования, который знает что-то вроде интерфейсов и полиморфизма подтипов. - person Lukas Eder; 10.07.2016

Цепочка методов может быть просто новинкой для большинства случаев, но я думаю, что она имеет место. Один из примеров можно найти в использовании Active Record в CodeIgniter:

$this->db->select('something')->from('table')->where('id', $id);

Это выглядит намного чище (и, на мой взгляд, имеет больше смысла), чем:

$this->db->select('something');
$this->db->from('table');
$this->db->where('id', $id);

Это действительно субъективно; Каждый человек имеет свое собственное мнение.

person Nathan    schedule 10.07.2009
comment
Это пример Fluent Interface с цепочкой методов, поэтому UseCase здесь немного отличается. Вы не просто связываете, но вы создаете внутренний язык, специфичный для предметной области, который легко читается. Кстати, ActiveRecord CI не является ActiveRecord. - person Gordon; 15.02.2012

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

Основные причины, по которым я его использую, - это как для удобства чтения, так и для предотвращения переполнения моего кода переменными.

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

Также это:

convertTextToVoice.LoadText ("source.txt"). ConvertToVoice ("destination.wav");

как я обычно его использую. Я обычно не использую его для связывания x параметров. Если бы я хотел указать x параметров при вызове метода, я бы использовал синтаксис params:

public void foo (params object [] items)

И приведите объекты на основе типа или просто используйте массив или коллекцию типов данных в зависимости от вашего варианта использования.

person Shane Thorndike    schedule 05.01.2018
comment
+1 к основному заблуждению состоит в том, что в целом думает, что это объектно-ориентированный подход, хотя на самом деле это скорее подход функционального программирования, чем что-либо еще. Известный вариант использования - это манипуляции с объектами без состояния (когда вместо изменения его состояния вы возвращаете новый объект, над которым продолжаете действовать). Вопросы OP и другие ответы показывают действия с отслеживанием состояния, которые действительно кажутся неудобными при объединении в цепочку. - person OmerB; 10.02.2019
comment
Да, вы правы, это манипуляция без состояния, за исключением того, что я обычно не создаю новый объект, а вместо этого использую внедрение зависимостей, чтобы сделать его доступной службой. И да, варианты использования с отслеживанием состояния - это не то, для чего, я думаю, была предназначена цепочка методов. Единственное исключение, которое я вижу, - это если вы инициализируете службу DI с некоторыми настройками и имеете какой-то сторожевой таймер для мониторинга состояния, например, какую-то службу COM. Просто ИМХО. - person Shane Thorndike; 28.04.2020

Я согласен, поэтому я изменил способ реализации свободного интерфейса в моей библиотеке.

До:

collection.orderBy("column").limit(10);

После:

collection = collection.orderBy("column").limit(10);

В реализации «до» функции модифицировали объект и заканчивали на return this. Я изменил реализацию на вернуть новый объект того же типа.

Моя причина для этого изменения:

  1. Возвращаемое значение не имело ничего общего с функцией, оно предназначалось исключительно для поддержки цепочки. Согласно ООП, это должна была быть функция void.

  2. Цепочка методов в системных библиотеках также реализует это таким образом (например, linq или string):

    myText = myText.trim().toUpperCase();
    
  3. Исходный объект остается нетронутым, позволяя пользователю API решать, что с ним делать. Это позволяет:

    page1 = collection.limit(10);
    page2 = collection.offset(10).limit(10);
    
  4. Реализацию копирования также можно использовать для создания объектов:

    painting = canvas.withBackground('white').withPenSize(10);
    

    Если функция setBackground(color) изменяет экземпляр и ничего не возвращает (как предполагалось).

  5. Поведение функций более предсказуемо (см. Пункты 1 и 2).

  6. Использование короткого имени переменной также может уменьшить беспорядок в коде, без принудительного использования API в модели.

    var p = participant; // create a reference
    p.addSchedule(events[1]);p.addSchedule(events[2]);p.setStatus('attending');p.save()
    

Заключение:
На мой взгляд, свободный интерфейс, использующий реализацию return this, просто неправильный.

person Bob Fanger    schedule 27.08.2011
comment
Но разве возврат нового экземпляра для каждого вызова не приведет к значительным накладным расходам, особенно если вы используете более крупные элементы? По крайней мере, для неуправляемых языков. - person Apeiron; 09.12.2011
comment
@Apeiron С точки зрения производительности это определенно быстрее, чем return this управлять, или иначе. Я считаю, что он неестественным образом получает свободный API. (Добавлена ​​причина 6: показать альтернативный вариант, не имеющий дополнительных / дополнительных функций) - person Bob Fanger; 10.12.2011
comment
Я согласен. В большинстве случаев лучше оставить базовое состояние DSL нетронутым и вместо этого возвращать новые объекты при каждом вызове метода ... Мне любопытно: о какой библиотеке вы упомянули? - person Lukas Eder; 05.01.2012

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

A.method1().method2().method3(); // one A

A.method1();
A.method2();
A.method3(); // repeating A 3 times

Это важно по той же причине, по которой СУХОЙ всегда имеет значение; если A оказывается ошибкой, и эти операции необходимо выполнить на B, вам нужно обновить только 1 место, а не 3.

Прагматически преимущество здесь невелико. Тем не менее, я возьму немного меньше набора текста, немного более надежный (СУХОЙ).

person Anfurny    schedule 01.04.2012
comment
Повторение имени переменной в исходном коде не имеет ничего общего с принципом DRY. DRY утверждает, что каждая часть знания должна иметь единственное, недвусмысленное, авторитетное представление в системе, или, другими словами, избегать повторения знаний (а не повторения текста ). - person pedromanoel; 12.06.2012
comment
Безусловно, повторение нарушает сухость. Повторение имен переменных (без необходимости) вызывает все зло так же, как и другие формы сухого выполнения: оно создает больше зависимостей и больше работы. В приведенном выше примере, если мы переименуем A, влажная версия потребует 3 изменений и вызовет ошибки для отладки, если любой из трех будет пропущен. - person Anfurny; 15.06.2012
comment
Я не вижу проблемы, которую вы указали в этом примере, поскольку все вызовы методов расположены близко друг к другу. Кроме того, если вы забудете изменить имя переменной в одной строке, компилятор вернет ошибку, и это будет исправлено до того, как вы сможете запустить свою программу. Кроме того, имя переменной ограничено областью ее объявления. Если эта переменная не является глобальной, что уже является плохой практикой программирования. ИМО, DRY не о том, чтобы меньше печатать, а о том, чтобы держать вещи изолированными. - person pedromanoel; 10.07.2012
comment
Компилятор может вернуть ошибку, или, возможно, вы используете PHP или JS, и интерпретатор может выдавать ошибку только во время выполнения, когда вы попадаете в это условие. - person Anfurny; 10.07.2012

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

1.)

participant
    .addSchedule(events[1])
    .addSchedule(events[2])
    .setStatus('attending')
    .save();

vs

participant.addSchedule(events[1]);
participant.addSchedule(events[2]);
participant.setStatus('attending');
participant.save()

2.)

participant
    .getSchedule('monday')
        .saveTo('monnday.file');

vs

mondaySchedule = participant.getSchedule('monday');
mondaySchedule.saveTo('monday.file');

3.)

participant
    .attend(event)
    .setNotifications('silent')
    .getSocialStream('twitter')
        .postStatus('Joining '+event.name)
        .follow(event.getSocialId('twitter'));

vs

participant.attend(event);
participant.setNotifications('silent')
twitter = participant.getSocialStream('twitter')
twitter.postStatus('Joining '+event.name)
twitter.follow(event.getSocialId('twitter'));

Как видите, вы почти ничего не выиграете, потому что вам нужно добавить разрывы строк в свой единственный оператор, чтобы сделать его более читабельным, и вам нужно добавить отступ, чтобы было ясно, что вы говорите о разных объектах. Что ж, если бы я хотел использовать язык, основанный на идентификации, я бы изучил Python вместо этого, не говоря уже о том, что большинство IDE удалит отступы путем автоматического форматирования кода.

Я думаю, что единственное место, где такой вид цепочки может быть полезен, - это конвейерные потоки в CLI или объединение нескольких запросов вместе в SQL. Оба имеют цену за несколько утверждений. Но если вы хотите решить сложные проблемы, вы столкнетесь даже с теми, кто заплатит цену и напишет код в виде нескольких операторов с использованием переменных или написанием сценариев bash и хранимых процедур или представлений.

Что касается интерпретации СУХОЙ: «Избегайте повторения знания (не повторения текста)». и «Меньше печатайте, даже не повторяйте тексты». Первое, что на самом деле означает этот принцип, но второе - распространенное недоразумение, потому что многие люди не могут понять слишком сложную чушь вроде «Каждая часть знания должна иметь единственное недвусмысленное выражение, авторитетное представительство в системе ». Второй - компактность любой ценой, что ломается в этом сценарии, потому что ухудшает читаемость. Первая интерпретация прерывается DDD, когда вы копируете код между ограниченными контекстами, потому что в этом сценарии более важна слабая связь.

person inf3rno    schedule 16.02.2019

Мнения Ответ

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

Некоторые вопросы:

  • Возвращают ли методы в цепочке новый объект или тот же объект видоизменился?
  • Все ли методы в цепочке возвращают один и тот же тип?
  • Если нет, то как обозначено изменение типа в цепочке?
  • Можно ли безопасно отбросить значение, возвращаемое последним методом?

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

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

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

person Eneko Alonso    schedule 30.05.2019
comment
полностью согласен. также создание методов, возвращающих исходный объект, кажется очень продуманным только для того, чтобы сделать синтаксис слащавым - возвращение исходного объекта не имеет ничего общего с фактической функциональностью и ответственностью методов imo. он использует возвращаемое значение ради синтаксиса за счет единственной ответственности / беспокойства. - person ahnbizcad; 15.03.2021

Добро:

  1. Это кратко, но позволяет элегантно уместить больше в одну строку.
  2. Иногда можно избежать использования переменной, что иногда может быть полезно.
  3. Он может работать лучше.

Плохо:

  1. Вы реализуете возврат, по существу добавляя функциональность к методам объектов, которые на самом деле не являются частью того, для чего предназначены эти методы. Он возвращает то, что у вас уже есть, исключительно для экономии нескольких байтов.
  2. Он скрывает переключение контекста, когда одна цепочка ведет к другой. Вы можете получить это с помощью геттеров, за исключением того, что это довольно ясно при переключении контекста.
  3. Объединение нескольких строк в цепочку выглядит некрасиво, плохо сочетается с отступами и может вызвать некоторую путаницу при обработке оператора (особенно в языках с ASI).
  4. Если вы хотите начать возвращать что-то еще, что полезно для связанного метода, вам, возможно, будет сложнее исправить это или столкнуться с большим количеством проблем с ним.
  5. Вы передаете управление сущности, которую вы обычно не передаете исключительно для удобства, даже в строго типизированных языках ошибки, вызванные этим, не всегда могут быть обнаружены.
  6. Он может работать хуже.

Общий:

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

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

К сожалению, это может быть неправильно использовано, например, вместо другого подхода (например, передача массива) или смешивания методов причудливыми способами (parent.setSomething (). GetChild (). SetSomething (). GetParent (). SetSomething ()).

person jgmjgm    schedule 29.09.2017

В типизированных языках (в которых отсутствует auto или эквивалент) это избавляет разработчика от необходимости объявлять типы промежуточных результатов.

import Participant
import Schedule

Participant participant = new Participant()
... snip...
Schedule s = participant.getSchedule(blah)
s.saveTo(filename)

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

Я считаю, что этот подход действительно разработан в Java, где а) все вызовы функций являются вызовами функций-членов и б) требуются явные типы. Конечно, здесь есть компромисс в терминах, когда теряется некоторая ясность, но в некоторых ситуациях некоторые люди считают это целесообразным.

person Dave    schedule 17.07.2020