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


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. Навіть якщо не кожен погоджується ..
Mercer Traieste

Вони також називають ці "вільні" методи або "вільний" інтерфейс. Ви можете оновити свою назву, щоб використовувати цей термін.
S.Lott

4
В іншому обговоренні ПС було сказано, що вільний інтерфейс - це більш широка концепція, яка пов'язана з читальністю коду, а ланцюжок методів - лише один із способів досягти цього. Однак вони тісно пов'язані, тому я додав тег та посилання на текстові інтерфейси - я вважаю, що їх достатньо.
Іларі Каясте

14
Як я придумав це, це те, що ланцюжок методів насправді є хиткою для отримання відсутньої функції в синтаксисі мови. Це дійсно не знадобиться, якби була вбудована альтернативна позначення, .яка б ігнорувала будь-які значення повернення mehtod і завжди викликала будь-які ланцюгові методи, використовуючи той самий об'єкт.
Іларі Каясте

Це чудова ідіома кодування, але, як і всі чудові інструменти, вона зловживається.
Мартін Спамер

Відповіді:


74

Я згоден, що це суб’єктивно. Здебільшого я уникаю ланцюжків методів, але останнім часом я також виявив випадок, коли це було саме правильне - у мене був метод, який приймав щось на зразок 10 параметрів і потребував більше, але здебільшого потрібно було лише вказати нечисленні. З перебоями це стало дуже громіздким дуже швидко. Натомість я обрав ланцюговий підхід:

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

Підхід до методу ланцюга був необов'язковим, але це полегшило написання коду (особливо з IntelliSense). Зауважте, що це один окремий випадок, і це не є загальною практикою в моєму коді.

Справа в тому, що - у 99% випадків ви, ймовірно, можете зробити так само добре або навіть краще, не будучи ланцюжком методів. Але є 1%, де це найкращий підхід.


4
ІМО, найкращий спосіб використовувати метод ланцюжка в цьому сценарії - це створити об'єкт параметра, який передається функції, наприклад P = MyObject.GetParamsObj().SomeParameter(asdasd).SomeOtherParameter(asdasd); Obj = MyObject.Start(); MyObject.Execute(P);. Ви маєте перевагу в тому, що зможете повторно використовувати цей параметр об'єкта в інших викликах, що є плюсом!
pedromanoel

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

3
Метод ланцюжка (як чоун в оригінальному питанні) вважається поганим, коли він порушує закон деметера . Дивіться: ifaceoughts.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)добре працює для мене.
Breandán Dalton

78

Всього мої 2 копійки;

Ланцюжок методів робить налагодження складним: - Ви не можете поставити точку перелому в стислій точці, щоб ви могли призупинити програму саме там, де вам потрібно. Якщо один із цих методів видає виняток, і ви отримаєте номер рядка, ви не маєте уявлення який метод у «ланцюжку» викликав проблему.

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

EDIT: коментар згадує, що ланцюжок методів та розрив рядків є окремими. Це правда. Однак, залежно від налагоджувача, в середині виписки може бути або не бути можливим розмістити точку розриву. Навіть якщо ви можете, використання окремих рядків з проміжними змінними дає вам набагато більше гнучкості та цілу купу значень, які ви можете вивчити у вікні «Перегляд», що допомагає налагоджувати процес.


4
Чи не використовуються нові рядки та методи ланцюжка незалежно? Ви можете зв'язати кожен виклик на новому рядку відповідно до @ Vilx-відповіді, і ви можете зазвичай розміщувати кілька окремих висловлювань на одній лінії (наприклад, використовуючи крапки з комою на Java).
брабстер

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. Однак метод ланцюжка найкращий, коли він використовується разом з іншими комбінаціями функцій.

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

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


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" лише один раз, полягає в тому, що тоді набагато простіше дати йому більш довгу, описову назву. Кожен раз, коли ім’я повинно з’являтися кілька разів у швидкій послідовності, є тенденція його скорочення (зменшити повторення та покращити читабельність), що робить його менш описовим, шкодить читабельності.
Кріс Додд

Хороший момент щодо читабельності та принципів DRY, зауважуючи, що ланцюжок методів запобігає самоаналізу повернутого значення даного методу та / або передбачає презумпцію порожніх списків / наборів та ненульових значень від кожного методу в ланцюзі (помилка викликає багато NPE в системах, які мені доводиться часто налагоджувати / виправляти).
Darrell Teague

@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

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)

Приниження ланцюга
тобто там, де я не люблю його використовувати

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

Існує також занепокоєння тим, що рутина повертає недійсні дані, тому я до цього часу використовував ланцюжок лише тоді, коли я повертав той самий екземпляр, який викликався. Як було зазначено, якщо ви здійснюєте ланцюжок між класами, ви робите налагодження важче (який з них повернувся нульовим?) І можете збільшити зв'язок залежностей між класами.

Висновок

Як і все в житті, і програмування, Ланцюг не є ні добрим, ні поганим, якщо ви можете уникнути поганого, тоді ланцюжок може бути великою користю.

Я намагаюся дотримуватися цих правил.

  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

Прив'язка методів може бути просто новинкою для більшості випадків, але я думаю, що це місце. Один приклад може бути знайдений у використанні програми ActiveI CodeIgniter :

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

Це виглядає набагато чистіше (і, на мій погляд, має більше сенсу), ніж:

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

Це дійсно суб'єктивно; У кожного своя думка.


Це приклад вільного інтерфейсу з методом ланцюга, тому UseCase тут трохи інший. Ви не просто ланцюжок, але ви створюєте внутрішню доменну мову, яка легко читається. З іншого боку, ActiveRecord CI не є ActiveRecord.
Гордон

3

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

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

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

Також це:

convertTextToVoice.LoadText ("source.txt"). ConvertToVoice ("призначення.wav");

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

публічний недійсний foo (params object [] items)

І передайте об'єкти на основі типу або просто використовуйте масив чи колекцію типу даних залежно від вашого випадку використання.


1
+1 про "первинна помилка - це думка, що це взагалі об'єктно-орієнтований підхід, коли насправді це більше підхід до функціонального програмування, ніж все інше". Видатний випадок використання - для маніпуляцій, що не мають стану, на об'єктах (де замість того, щоб змінити його стан, ви повернете новий об'єкт, на який ви продовжуєте діяти). Питання та інші відповіді ОП демонструють наполегливі дії, які справді здаються незручними з прикуванням.
OmerB

Так, ви маєте рацію, це маніпуляція без стану, за винятком того, що я зазвичай не створюю новий об'єкт, але використовую замість того, щоб зробити його доступною послугою. І так, випадкові випадки використання не є тим, на що я думаю, що це було призначене для ланцюжка методів. Єдиний виняток, який я бачу, це якщо ви ініціалізуєте службу DI з деякими налаштуваннями і маєте якусь сторожову собаку для моніторингу стану, як-от послуга COM-якого типу. Просто ІМХО.
Шейн Торндайк

2

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

Перед:

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

Після:

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

У реалізації "до" функції змінили об'єкт і закінчилися return this. Я змінив реалізацію, щоб повернути новий об'єкт того ж типу .

Мої міркування щодо цієї зміни :

  1. Повернене значення не мало нічого спільного з функцією, воно суто там, щоб підтримувати ланцюгову частину. Це повинно було бути недійсною функцією згідно з OOP.

  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 Виконання продуктивності, безумовно, швидше return thisкерувати чи іншим чином. На мою думку, він отримує вільну api "неприродним" способом. (Додано причину 6: показати нескладну альтернативу, яка не має накладних / додаткових функціональних можливостей)
Bob Fanger

Я погоджуюсь з тобою. В основному краще залишити базовий стан DSL недоторканим і повернути нові об'єкти під час кожного виклику методу, замість цього ... Мені цікаво: Що це за бібліотека, яку ви згадали?
Лукас Едер

1

Тут повністю пропущений момент - це те, що ланцюжок методів дозволяє сушити . Це ефективний режим "з" (який у деяких мовах погано реалізований).

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

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

Це має значення з тієї самої причини, що DRY завжди має значення; якщо A виявляється помилкою, і ці операції потрібно виконувати на B, вам потрібно оновити лише 1 місце, а не 3.

Прагматично перевага в цьому випадку невелика. І все-таки трохи менше набравши текст, більш міцний (DRY), я візьму його.


14
Повторення імені змінної у вихідному коді не має нічого спільного з принципом DRY. DRY стверджує, що "Кожен предмет повинен мати єдине, недвозначне, авторитетне представлення в системі", або іншими словами, уникати повторення знань (а не повторення тексту ).
pedromanoel

4
Це, безумовно, повторення, яке порушує сухе. Повторення змінних імен (без необхідності) спричиняє все зло так само, як це роблять інші форми сухих: Це створює більше залежностей і більше роботи. У наведеному вище прикладі, якщо ми перейменовуємо A, у мокрій версії знадобляться 3 зміни та призведе до помилок, якщо будь-яка з трьох буде пропущена.
Анфурн

1
Я не бачу проблеми, яку ви вказали в цьому прикладі, оскільки всі виклики методів близькі один до одного. Крім того, якщо ви забудете змінити ім'я змінної в одному рядку, компілятор поверне помилку, і це буде виправлено до запуску програми. Також назва змінної обмежена сферою її декларування. Якщо ця змінна не є глобальною, це вже погана практика програмування. IMO, DRY - це не про те, що типізувати менше, а про те, щоб тримати речі ізольованими.
pedromanoel

"Компілятор" може повернути помилку, або, можливо, ви використовуєте PHP або JS, і там інтерпретатор може видавати помилку під час виконання лише під час досягнення цієї умови.
Анфурний

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-скрипти та зберігаючи процедури або представлення.

Щодо інтерпретацій DRY: "Уникайте повторення знань (а не повторення тексту)". і "Набирайте менше, навіть не повторюйте тексти". Перший - що саме означає цей принцип, але другий - загальне непорозуміння, оскільки багато людей не можуть зрозуміти надскладне глупство, наприклад "Кожен предмет повинен мати єдине, однозначне, авторитетне представництво в системі ". Другий - це компактність за будь-яку ціну, яка порушує цей сценарій, оскільки погіршує читабельність. Перша інтерпретація порушується методом DDD, коли ви копіюєте код між обмеженими контекстами, оскільки в цьому сценарії важливіша розв'язка.


0

Добре:

  1. Це лаконічно, але дозволяє елегантно вкласти в одну лінію.
  2. Іноді можна уникнути використання змінної, яка може бути корисною.
  3. Це може бути краще.

Погане:

  1. Ви реалізуєте повернення, фактично додаючи функціональність методам на об'єктах, які насправді не є частиною того, що ці методи призначені робити. Це повернення чогось, у вас уже є суто, щоб зберегти кілька байт.
  2. Він приховує контекстні комутатори, коли один ланцюг веде до іншого. Ви можете отримати це за допомогою гетерів, за винятком того, що це досить зрозуміло, коли контекст змінюється.
  3. Пов'язування декількох ліній виглядає некрасиво, не грає добре з відступом і може викликати плутанину в роботі оператора (особливо на мовах з ASI).
  4. Якщо ви хочете почати повертати щось інше, що корисне для ланцюгового методу, вам, можливо, буде важче це виправити, або зіткнуться з цим більше проблем.
  5. Ви перевантажуєте управління суттю, яке зазвичай не вивантажується для зручності, навіть у строго набраних мовах помилки, викликані цим, не завжди можуть бути виявлені.
  6. Це може працювати гірше.

Загальне:

Хорошим підходом є не використовувати ланцюги взагалі, поки не виникнуть ситуації або конкретні модулі будуть особливо пристосовані до цього.

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

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


0

Відповідна думка

Найбільшим недоліком ланцюга є те, що читачеві може бути важко зрозуміти, як кожен метод впливає на вихідний об'єкт, якщо він є, і який тип повертає кожен метод.

Деякі питання:

  • Чи повертають методи в ланцюжку новий об'єкт або той самий об'єкт мутують?
  • Чи всі методи в ланцюжку повертаються одного типу?
  • Якщо ні, як вказується, коли тип ланцюга змінюється?
  • Чи можна безпечно відкинути значення, повернені останнім методом?

Налагодження, в більшості мов, дійсно може бути складніше з ланцюжком. Навіть якщо кожен крок ланцюга знаходиться по своїй лінії (який тип перемоги призначає ланцюг), може бути важко перевірити значення, що повертаються після кожного кроку, спеціально для не мутуючих методів.

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

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


0

У мовах, що вводяться (які відсутні autoабо еквівалентні) це рятує реалізатора від необхідності оголошувати типи проміжних результатів.

import Participant
import Schedule

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

Якщо довші ланцюги можуть мати справу з декількома проміжними типами, вам потрібно буде оголосити кожен з них.

Я вважаю, що цей підхід дійсно розроблений на Яві, де а) всі виклики функцій - це виклики функцій членів, і б) потрібні явні типи. Звичайно, тут йдеться про компроміс у плані, втрачаючи деяку явність, але в деяких ситуаціях деякі люди вважають, що це варто.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.