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


151

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

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, тогда как предыдущие вызовы относятся к участнику. Может быть, это плохая практика - писать цепочки с изменяющимся контекстом (не так ли?), Но даже тогда вам придется постоянно проверять, действительно ли точечные цепочки, которые выглядят одинаково, соответствуют одному и тому же контексту или работают только с результатом. ,

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

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


Кажется, что цепочка методов - это как минимум один из способов написания умного кода на Java. Даже если не каждый согласится ..
Мерсер Трайест

Они также называют эти «свободные» методы или «свободный» интерфейс. Вы можете обновить свой заголовок, чтобы использовать этот термин.
S.Lott

4
В другом обсуждении SO было сказано, что свободный интерфейс - это более масштабная концепция, связанная с читабельностью кода, и объединение методов - только один из способов достижения этой цели. Они тесно связаны между собой, поэтому я добавил теги и ссылки на свободно распространяемые интерфейсы в тексте - я думаю, что этого достаточно.
Илари Каясте

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

Это отличная идиома кодирования, но, как и все отличные инструменты, ею злоупотребляют.
Мартин Спамер

Ответы:


74

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

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

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

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


4
IMO, лучший способ использовать цепочку методов в этом сценарии - это создать объект параметра для передачи в функцию, например P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);. У вас есть преимущество в возможности повторного использования этого объекта параметра в других вызовах, что является плюсом!
Pedromanoel

20
Просто мой вклад в модели, фабричный метод обычно имеет только одну точку создания, и продукты выбираются статически на основе параметров фабричного метода. В создании цепи выглядит более шаблон Builder , где вы вызываете различные методы , чтобы получить результат, методы могут быть необязательными , как и в цепочках Method , мы можем иметь что - то вроде PizzaBuilder.AddSauce().AddDough().AddTopping()больше ссылок здесь
Marco Медраны

3
Метод цепочки (как показано в исходном вопросе) считается плохим, если он нарушает закон Деметры . См .: ifaceblyts.net/2006/03/07/… Ответ, приведенный здесь, фактически следует этому закону, поскольку он является «образцом строителя».
Angel O'Sphere

2
@Marco Medrano Пример PizzaBuilder всегда меня раздражал, так как я читал его в JavaWorld давным-давно. Я чувствую, что должен добавить соус к моей пицце, а не к своему шеф-повару.
Бреандан Далтон

1
Я знаю, что ты имеешь в виду, Вилкс. Но все же, когда я читаю list.add(someItem), я читаю это как «этот код добавляет someItemк listобъекту». поэтому, когда я читаю PizzaBuilder.AddSauce(), я читаю это естественно как «этот код добавляет соус к PizzaBuilderобъекту». Другими словами, я вижу оркестратор (который выполняет добавление) как метод, в который встроен код list.add(someItem)или PizzaBuilder.addSauce(). Но я просто думаю, что пример PizzaBuilder немного искусственный. Ваш пример MyObject.SpecifySomeParameter(asdasd)хорошо работает для меня.
Бреандан Далтон

78

Просто мои 2 цента;

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

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

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


4
Разве использование новых строк и методов не является независимым? Вы можете связывать каждый вызов по новой строке в соответствии с @ Vilx-answer, и обычно вы можете поместить несколько отдельных операторов в одну строку (например, используя точки с запятой в Java).
brabster

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

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

4
+1, вы никогда не знаете, что возвращал каждый метод при пошаговой отладке цепочки методов. Шаблон цепочки методов - это хак. Это худший кошмар программиста обслуживания.
Ли Ковальковски

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

39

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

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

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

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

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

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


Если есть шанс , что Participantне является допустимым , Scheduleто getScheduleметод предназначен для возврата Maybe(of Schedule)типа и saveToметод предназначен для приема Maybeтипа.
Лайтман

24

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

Метод цепочки

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

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

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

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


2
Ссылка не работает :(
Qw3ry

Что вы имеете в виду под DSL? Специфичный для домена язык
Sören

@ Sören: Фаулер относится к предметно-ориентированным языкам.
Дирк

21

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

Как:

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

9
Это более кратко, поскольку вы избегаете двух частей 'someList' даже в первом примере и в итоге получаете одну строку вместо трех. Теперь, если это на самом деле хорошо или плохо, зависит от разных вещей и, возможно, дело вкуса.
Фабиан Стиг

26
Реальное преимущество наличия «someList» только один раз заключается в том, что тогда гораздо проще дать ему более длинное, более описательное имя. В любое время, когда имя должно появляться несколько раз в быстрой последовательности, существует тенденция сокращать его (уменьшить количество повторений и улучшить читаемость), что делает его менее информативным и ухудшает читаемость.
Крис Додд

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

@ChrisDodd Никогда не слышал об автозаполнении?
inf3rno

1
«человеческий мозг очень хорошо распознает повторение текста, если у него одинаковые отступы», - именно в этом и заключается проблема с повторением: он заставляет мозг сосредоточиться на повторяющейся схеме. Таким образом, чтобы прочитать И ПОНЯТЬ код, вы должны заставить свой мозг заглядывать за / за повторяющимся шаблоном, чтобы увидеть, что на самом деле происходит, то есть в различиях между повторяющимся шаблоном, который ваш мозг стремится замаскировать. Это ПОЧЕМУ повторение так плохо для читабельности кода.
Крис Додд

7

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

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

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

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

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

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

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

Это, как говорится, мне лично нравится цеплять их при программировании Ruby.


7

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

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


6

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

Метод цепочки не является чем-то плохим или хорошим по своей сути.

Читаемость это самая важная вещь.

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


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

Разве это не было бы плохо по своей сути, если бы это привело к сильной связи? Разбиение цепочки на отдельные операторы не снижает читабельность.
aberrant80

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

6

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

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

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

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

против

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. Используйте это, когда это делает код проще

6

Объединение методов может позволить непосредственно создавать расширенные 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 );

@ user877329: Да, его можно использовать практически в любом объектно-ориентированном языке программирования, который знает что-то вроде интерфейсов и полиморфизма подтипов
Лукас Эдер,

4

В большинстве случаев цепочка методов может быть новинкой, но я думаю, что она имеет место. Один пример может быть найден в использовании 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);

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


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

3

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

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

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

Также это:

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

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

public void foo (params object [] items)

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


1
+1 на «основная ошибка - думать, что это вообще объектно-ориентированный подход, хотя на самом деле это скорее подход функционального программирования, чем все остальное». Известный вариант использования предназначен для манипуляций без состояния на объектах (где вместо изменения его состояния вы возвращаете новый объект, над которым продолжаете действовать). Вопросы OP и другие ответы показывают действия с состоянием, которые действительно кажутся неловкими с цепочкой.
OmerB

Да, вы правы, это манипуляция без состояния, за исключением того, что я обычно не создаю новый объект, а вместо этого использую внедрение зависимостей, чтобы сделать его доступным сервисом. И да, варианты использования с сохранением состояния - это не то, для чего, я думаю, была создана цепочка методов. Единственное исключение, которое я вижу, - это если вы инициализируете службу DI с некоторыми настройками и имеете какой-то сторожевой таймер для мониторинга состояния, например, какой-либо COM-сервис. Просто ИМХО.
Шейн Торндайк

2

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

Перед:

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

После:

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

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

Мои аргументы в пользу этого изменения :

  1. Возвращаемое значение не имело ничего общего с функцией, оно было просто для поддержки цепочки, согласно OOP это должна была быть функция 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реализацию, просто неверен.


1
Но не приведет ли возврат нового экземпляра для каждого вызова к дополнительным затратам, особенно если вы используете более крупные элементы? По крайней мере, для неуправляемых языков.
Apeiron

@Apeiron Производительность это определенно быстрее, чем return thisуправлять или иначе. По моему мнению, он получает свободный API "неестественным" способом. (Добавлена ​​причина 6: показать не беглую альтернативу, которая не имеет служебных / дополнительных функций)
Боб Фангер,

Я согласен с тобой. В большинстве случаев лучше оставить базовое состояние DSL нетронутым и возвращать новые объекты при каждом вызове метода, вместо этого ... Мне любопытно: что за библиотеку вы упомянули?
Лукас Эдер

1

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

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

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

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

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


14
Повторение имени переменной в исходном коде не имеет ничего общего с принципом СУХОЙ. DRY заявляет, что «каждая часть знаний должна иметь одно, однозначное, авторитетное представление в системе», или, другими словами, избегать повторения знаний (не повторения текста ).
педроманоэль

4
Это, безусловно, повторение, которое нарушает сухой. Повторение имен переменных (без необходимости) причиняет все зло точно так же, как это делают другие формы сухого поведения: оно создает больше зависимостей и больше работы. В приведенном выше примере, если мы переименуем A, для мокрой версии потребуется 3 изменения, что приведет к отладке ошибок в случае пропуска одной из трех.
Anfurny

1
Я не вижу проблемы, которую вы указали в этом примере, так как все вызовы методов близки друг к другу. Кроме того, если вы забудете изменить имя переменной в одной строке, компилятор вернет ошибку, и это будет исправлено, прежде чем вы сможете запустить вашу программу. Кроме того, имя переменной ограничено областью ее объявления. Если эта переменная не является глобальной, что уже является плохой практикой программирования. ИМО, СУХОЙ - это не меньше печатать, а держать вещи в изоляции.
Педроманоэль

«Компилятор» может вернуть ошибку, или, может быть, вы используете PHP или JS, и там интерпретатор может выдавать ошибку только во время выполнения, когда вы выполняете это условие.
Anfurny

1

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

1.)

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

против

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

2.)

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

против

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

3.)

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

против

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


0

Хорошо:

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

Плохо:

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

Общее:

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

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

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


0

Мнение Ответ

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

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

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

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

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

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


0

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

import Participant
import Schedule

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

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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.