Метод цепочки - почему это хорошая практика или нет? - 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 ]

3 голосов
/ 05 января 2018

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

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

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

Также это:

.

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

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

public void foo (params object [] items)

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

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

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

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

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

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

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

2 голосов
/ 27 августа 2011

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

До:

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

После того, как:

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

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

Мое обоснование этого изменения :

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

  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 голос
/ 17 февраля 2019

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

1.)

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

против

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

2.)

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

против

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

3.)

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

против

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

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

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

Что касается СУХОЙ интерпретации: «Избегайте повторения знания (не повторения текста)». и «Напечатывай меньше, даже не повторяй тексты». Первое, что на самом деле означает этот принцип, но второе - это общее недоразумение, потому что многие люди не могут понять слишком сложную чушь, как «Каждый кусочек знания должен иметь один, однозначный авторитетное представительство в системе ". Второй - это компактность любой ценой, что нарушает этот сценарий, поскольку ухудшает читаемость. Первая интерпретация нарушается DDD, когда вы копируете код между ограниченными контекстами, потому что слабая связь более важна в этом сценарии.

1 голос
/ 01 апреля 2012

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

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

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

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

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

0 голосов
/ 31 мая 2019

Мнение Ответа

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

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

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

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

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

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

0 голосов
/ 29 сентября 2017

благо:

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

Плохое:

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

Общие сведения:

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

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

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

...