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


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

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

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

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

15 127

15 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Мартин Фаулер имеет хорошую дискуссию здесь:

Способ Сцепления

когда

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

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

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

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

как:

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

лучше:

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

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

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

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

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

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

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

вы находитесь в зависимости не только от Гастроном как вы, но также обратится к локальному хранилищу.

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

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

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

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

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

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

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

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

вывод

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

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

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

метод цепочки может позволить для проектирования advanced 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 );

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

цепочка методов - это не то, что по своей сути плохо или хорошо ИМО.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

перед:

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

после:

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

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

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

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

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

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

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

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

плохое:

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

общие:

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

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

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

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

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

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

кроме этого:

convertTextToVoice.LoadText ("источник.формат txt.)"ConvertToVoice ("пункт назначения.wav");

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

public void foo (params object [] items)

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