Метод цепочки - почему это хорошая практика или нет? - PullRequest
140 голосов
/ 09 июля 2009

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

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

Ответы [ 17 ]

75 голосов
/ 28 октября 2010

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

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

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

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

70 голосов
/ 09 июля 2009

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

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

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

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

38 голосов
/ 10 июля 2009

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

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

23 голосов
/ 09 июля 2009

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

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

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

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

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

Самая большая проблема для Метода Цепочка является конечной проблемой. Пока есть обходные пути, обычно если вы столкнетесь с этим, вам лучше Использование вложенной функции. Вложенные Функция также является лучшим выбором, если вы попали в беспорядок с Контекстные переменные.

20 голосов
/ 09 июля 2009

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

Как это:

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")
8 голосов
/ 09 июля 2009

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

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

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

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

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

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

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

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

6 голосов
/ 10 июля 2009

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

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

6 голосов
/ 09 июля 2009

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

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

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

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

6 голосов
/ 05 января 2012

Метод цепочки может позволить разрабатывать расширенные 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 );
6 голосов
/ 20 мая 2011

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

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

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

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

Vs Что-то вроде

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