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

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

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

Ответ 1

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

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

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

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

Ответ 2

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

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

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

EDIT: комментарий упоминает, что метод цепочки и прерывание строк являются отдельными. Это правда. Однако, в зависимости от отладчика, может оказаться или не удастся разместить точку останова в середине инструкции. Даже если вы можете, используя отдельные строки с промежуточными переменными, вы получаете гораздо большую гибкость и целую кучу значений, которые вы можете проверить в окне "Часы", что помогает в процессе отладки.

Ответ 3

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

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

Ответ 4

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

Цепочка методов

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

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

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

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

Ответ 5

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

Как это сделать:

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

Ответ 6

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

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

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

Итак, когда вы вызываете foodStore.getLocalStore(параметры).getPriceforProduct(что-либо)

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

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

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

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

Ответ 7

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

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

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

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

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

  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)

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

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

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

Заключение

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

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

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

Ответ 8

Цепочка метода может позволить непосредственно разрабатывать расширенные // 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, как это делается , библиотекой, которая Я создал. См. Довольно сложный пример 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()

Другим приятным примером является , небольшая 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 );

Ответ 9

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

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

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

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

Ответ 10

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

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

Ответ 11

Пропущенная точка здесь - это то, что цепочка методов позволяет DRY. Это эффективная поддержка для "с" (которая плохо реализована на некоторых языках).

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

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

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

Прагматично преимущество в этом случае мало. Тем не менее, немного меньше набрав, litle более надежный (DRY), я возьму его.

Ответ 12

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

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

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

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

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

Ответ 13

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

До:

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

После:

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

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

Мое рассуждение об этом изменении:

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

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

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

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

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

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

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

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

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

Вывод:
По-моему, свободный интерфейс, использующий реализацию return this, просто неверен.

Ответ 14

Хорошее:

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

Плохо:

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

Общие:

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

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

В случае акцента это может быть неправильно использовано, например, вместо другого подхода (например, для передачи массива) или методов смешивания (parent.setSomething(). getChild(). setSomething(). getParent(). setSomething ( )).

Ответ 15

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

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

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

Также это:

.

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

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

public void foo (объект params [])

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