Есть ли название для (анти) шаблона передачи параметров, который будет использоваться только на нескольких уровнях в цепочке вызовов?


209

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

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

higherlevel(newParam)->level1(newParam)->level2(newParam)->level3(newParam)

где newParamв моем примере ранее была глобальная переменная, но вместо этого это могло быть ранее жестко закодированное значение. Дело в том, что теперь значение newParam получено в higherlevel()и должно «путешествовать» вплоть до level3().

Мне было интересно, есть ли имя (имена) для такого рода ситуации / паттерна, где вам нужно добавить параметр во многие функции, которые просто «передают» значение без изменений.

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


94
Это улучшение по сравнению с использованием глобальных переменных. Он четко показывает, от какого состояния зависит каждая функция (и это один шаг на пути к чистым функциям). Я слышал, что это называется «продвижение» параметра через, но я не знаю, насколько распространена эта терминология.
садовод

8
Это слишком широкий спектр, чтобы иметь конкретный ответ. На этом уровне я бы назвал это просто «кодированием».
Мачадо

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

7
Хотя я ценю дискуссию о том, является ли это хорошим шаблоном / антипаттерном / концепцией / решением, я действительно хотел бы знать, есть ли для него название.
ecerulm

3
Я также слышал , что называется резьб наиболее часто, но и водопровод , как и в опускании отвес в течение стека вызовов.
wchargin

Ответы:


202

Сами данные называются «данные бродяги» . Это «запах кода», указывающий на то, что один фрагмент кода взаимодействует с другим фрагментом кода на расстоянии через посредников.

  • Увеличивает жесткость кода, особенно в цепочке вызовов. Вы гораздо более ограничены в способе рефакторинга любого метода в цепочке вызовов.
  • Распространяет знания о данных / методах / архитектуре в местах, которые не заботятся об этом. Если вам нужно объявить данные, которые только проходят, а объявление требует нового импорта, вы загрязнили пространство имен.

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


73
В результате поиска «данных бродяги» мне удалось найти книгу «Code Complete» в моей подписке Safari. В книге есть раздел под названием «Причины использования глобальных данных», и одна из причин - «Использование глобальных данных может исключить бродячие данные». :) Я чувствую, что «данные бродяги» позволят мне найти больше литературы о работе с глобальными. Спасибо!
ecerulm

9
@JimmyJames, эти функции делают вещи, конечно. Только не с тем конкретным новым параметром, который раньше был просто глобальным.
ecerulm

174
За 20 лет программирования я буквально никогда не слышал этот термин раньше, и не было бы сразу очевидно, что он имел в виду. Я не жалуюсь на ответ, просто предполагаю, что этот термин не так широко используется / известен. Может быть, это только я.
Дерек Элкинс

6
Некоторые глобальные данные в порядке. Вместо того, чтобы называть это «глобальными данными», вы можете называть это «средой» - потому что это то, что есть. Окружение может включать, например, строковый путь для appdata (в окнах) или в моем текущем проекте весь набор кистей GDI +, перьев, шрифтов и т. Д., Используемых всеми компонентами.
Робинсон

7
@ Робинсон Не совсем. Например, действительно ли вы хотите, чтобы ваш код написания изображений касался% AppData%, или вы бы предпочли, чтобы он использовал аргумент для того, где писать? В этом разница между глобальным состоянием и аргументом. «Окружающая среда» может так же легко быть зависимой, вводимой только для тех, кто отвечает за взаимодействие со средой. Кисти GDI + и т. Д. Более разумны, но на самом деле это скорее случай управления ресурсами в среде, которая не может сделать это для вас - в значительной степени это недостаток базовых API и / или вашего языка / библиотек / среды выполнения.
Luaan

102

Я не думаю, что это само по себе является анти-паттерном. Я думаю, проблема в том, что вы думаете о функциях как о цепочке, тогда как на самом деле вы должны думать о каждой из них как о независимом черном ящике ( ПРИМЕЧАНИЕ : рекурсивные методы являются заметным исключением из этого совета).

Например, скажем, мне нужно вычислить количество дней между двумя календарными датами, поэтому я создаю функцию:

int daysBetween(Day a, Day b)

Для этого я создаю новую функцию:

int daysSinceEpoch(Day day)

Тогда моя первая функция становится просто:

int daysBetween(Day a, Day b)
{
    return daysSinceEpoch(b) - daysSinceEpoch(a);
}

В этом нет ничего против паттерна. Параметры метода daysBetween передаются другому методу, и на него никогда не ссылаются в методе, но они все еще необходимы для того, чтобы этот метод делал то, что ему нужно.

Я бы порекомендовал посмотреть на каждую функцию и начать с пары вопросов:

  • Есть ли у этой функции четкая и сфокусированная цель или это метод «делать что-то»? Обычно здесь помогает имя функции, и если в нем есть материал, который не описан именем, это красный флаг.
  • Слишком много параметров? Иногда метод может на законных основаниях нуждаться в большом количестве входных данных, но наличие такого большого количества параметров делает его обременительным для использования или понимания.

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

Если у вас просто слишком много параметров, рассмотрите рефакторинг Method to Object .


2
Ну, я не хотел спорить с (анти). Но я все еще задаюсь вопросом, есть ли название для «ситуации» необходимости обновления многих сигнатур функций. Я думаю, это скорее «запах кода», чем антипаттерн. Он говорит мне, что есть что-то, что нужно исправить в этом устаревшем коде, если мне нужно обновить сигнатуру 6 функций, чтобы приспособиться к уничтожению глобала. Но я думаю, что передача параметров, как правило, в порядке, и я ценю советы о том, как решить основную проблему.
ecerulm

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

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

3
@ecerulm Я не думаю, что есть одно имя для этого. Это похоже на симптом, который часто встречается при многих заболеваниях, а также при других заболеваниях, например, «сухость во рту». Если вы уточните описание структуры кода, это может указывать на что-то конкретное.
JimmyJames

@ecerulm Это говорит о том, что есть что-то, что нужно исправить - теперь гораздо более очевидно, что что-то нужно исправить, чем когда это было глобальной переменной.
immibis

61

BobDalgleish уже отметил, что этот (анти) паттерн называется « бродяга данных ».

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

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

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

playerName = "Bob"
playerEyeColor = GREEN
playerXPosition = -8
playerYPosition = 136
playerHealth = 100
playerMaxHealth = 100

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

mainGameLoop()
 -> processInputEvent()
     -> doPlayerAction()
         -> movePlayer()
             -> checkCollision()
                 -> interactWithNPC()
                     -> interactWithShopkeeper()

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

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

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

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

Вместо этого решение состоит в том, чтобы объекты хранили ссылки на любые другие объекты, с которыми они имеют постоянные или временные отношения. Так, например, объект игрока (и, возможно, любые объекты NPC тоже), вероятно, должен хранить ссылку на объект «игровой мир», который будет иметь ссылку на текущий уровень / карту, так что методу, подобному методу player.moveTo(x, y), не нужно явно указывать карту в качестве параметра.

Точно так же, если бы наш персонаж игрока имел, скажем, собаку, которая следовала за ним, мы естественным образом сгруппировали бы все переменные состояния, описывающие собаку, в один объект, и дали бы объекту игрока ссылку на собаку (так, чтобы игрок мог скажем, назовите собаку по имени) и наоборот (чтобы собака знала, где находится игрок). И, конечно, мы, вероятно, хотели бы сделать объекты player и dog подклассами более общего объекта «актера», чтобы мы могли повторно использовать один и тот же код, скажем, для перемещения по карте.

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


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

6
@JimmyJames: Ваше мнение о полиморфизме хорошее, и я подумал о том, чтобы сделать это сам, но оставил это, чтобы ответ не удлинился. То, что я пытался (возможно, плохо) сделать, было то, что, хотя чисто с точки зрения потока данных есть небольшая разница между, foo.method(bar, baz)и method(foo, bar, baz)есть другие причины (в том числе полиморфизм, инкапсуляция, локальность и т. Д.), Чтобы предпочесть первое.
Ильмари Каронен

@IlmariKaronen: также очень очевидное преимущество, заключающееся в том, что оно защищает прототипы функций от любых будущих изменений / дополнений / удалений / рефакторингов в объектах (например, playerAge). Одно это бесценно.
августа

34

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

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

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

  • так что в качестве компромисса вы часто можете сделать такой параметр переменной-членом в одном или нескольких классах, и это то, что я бы назвал правильным дизайном класса .


10
+1 за правильный дизайн класса. Это звучит как классическая проблема в ожидании решения ОО.
10

21

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

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

Sandwich make_sandwich() {
    PeanutButter pb = get_peanut_butter();
    Jelly j = get_jelly();
    return pb + j;
}
extern PhysicalRefrigerator g_refrigerator;
PeanutButter get_peanut_butter() {
    return g_refrigerator.get("peanut butter");
}
Jelly get_jelly() {
    return g_refrigerator.get("jelly");
}

но было бы лучше применить инъекцию зависимостей и написать так:

Sandwich make_sandwich(Refrigerator& r) {
    PeanutButter pb = get_peanut_butter(r);
    Jelly j = get_jelly(r);
    return pb + j;
}
PeanutButter get_peanut_butter(Refrigerator& r) {
    return r.get("peanut butter");
}
Jelly get_jelly(Refrigerator& r) {
    return r.get("jelly");
}

Теперь у вас есть функция, которая четко документирует все свои зависимости в сигнатуре функции, что очень удобно для удобства чтения. В конце концов, это правда, что для make_sandwichвас нужен доступ к Refrigerator; таким образом, старая сигнатура функции была в основном неискренней, поскольку не принимала холодильник за входные данные.

В качестве бонуса, если вы правильно делаете иерархию классов, избегаете нарезки и т. Д., Вы можете даже выполнить модульное тестирование make_sandwichфункции, передавая MockRefrigerator! (Вам может понадобиться выполнить юнит-тестирование таким образом, потому что ваша среда юнит-тестирования может не иметь доступа ни к одному из них PhysicalRefrigerator.)

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


10
Это очень явно анти паттерн. Нет абсолютно никакого требования к холодильнику, который будет передан. Передача общего IngredientSource может сработать, но что, если вы получите хлеб из мусорной корзины, тунец из кладовой, сыр из холодильника ... путем введения зависимости от источника ингредиентов в несвязанную операцию формирования этих ингредиенты в бутерброд, вы нарушили разделение проблем и сделали запах кода.
Деви Морган

8
@DewiMorgan: Очевидно, что вы можете продолжить рефакторинг, чтобы обобщить Refrigeratorв IngredientSourceили даже обобщить понятие «сэндвич» в template<typename... Fillings> StackedElementConstruction<Fillings...> make_sandwich(ElementSource&); это называется «универсальное программирование», и оно достаточно мощное, но, безусловно, оно гораздо более загадочно, чем ОП действительно хочет попасть прямо сейчас. Не стесняйтесь, чтобы открыть новый вопрос о надлежащем уровне абстракции для сэндвич-программ. ;)
Quuxplusone

11
Если нет ошибок, непривилегированный пользователь не должен иметь доступа к make_sandwich().
dotancohen


19
Самая серьезная ошибка в вашем коде заключается в том, что вы храните арахисовое масло в холодильнике.
Мальволио,

15

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


3
Если level3()нужно newParam, это наверняка, но каким-то образом разные части кода должны взаимодействовать друг с другом. Я не обязательно назвал бы параметр функции плохой связью, если эта функция использует параметр. Я думаю, что проблемным аспектом цепочки является дополнительная муфта, введенная для level1()и level2()не имеющая смысла, newParamкроме как для передачи ее. Хороший ответ, +1 за сцепление.
ноль

6
@null Если бы они действительно не использовали его, они могли бы составить значение вместо того, чтобы получать от своего абонента.
Random832

3

Хотя этот ответ не дает прямого ответа на ваш вопрос, я чувствую, что было бы упущением пропустить его, не упоминая, как его улучшить (поскольку, как вы говорите, это может быть анти-паттерном). Я надеюсь, что вы и другие читатели сможете извлечь пользу из этого дополнительного комментария о том, как избежать «бродячих данных» (как Боб Далглиш так услужливо назвал их для нас).

Я согласен с ответами, которые предлагают сделать что-то большее ОО, чтобы избежать этой проблемы. Тем не менее, другой способ также помочь значительно сократить эту передачу аргументов, не переходя к « просто передать класс, в котором вы передавали много аргументов! », - это рефакторинг, чтобы некоторые этапы вашего процесса происходили на более высоком уровне, а не на более низком. один. Например, вот некоторые перед кодом:

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   FilterAndReportStuff(stuffs, desiredName);
}

public void FilterAndReportStuff(IEnumerable<Stuff> stuffs, string desiredName) {
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   ReportStuff(stuffs.Filter(filter));
}

public void ReportStuff(IEnumerable<Stuff> stuffs) {
   stuffs.Report();
}

Обратите внимание, что это становится еще хуже, чем больше вещей, которые нужно сделать в ReportStuff. Возможно, вам придется передать экземпляр Reporter, который вы хотите использовать. И все виды зависимостей, которые должны быть переданы, функционируют как вложенные функции.

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

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   var filteredStuffs = stuffs.Filter(filter)
   filteredStuffs.Report();
}

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

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

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

Недавно я попытался добавить модульные тесты в класс (который я не писал), который занимал что-то вроде 17 зависимостей, и все они должны были быть подвергнуты насмешке! Я еще не все понял, но я разделил класс на три класса, каждый из которых имел дело с одним из существительных, к которым он обращался, и получил список зависимостей до 12 для худшего и около 8 для лучший.

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


2

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


1
Немного слабоват в деталях, что, вероятно, объясняет отрицательные стороны. Тем не менее, в духе этот ответ точно на месте: ОП должен прочитать Закон Деметры - это соответствующий термин.
Конрад Рудольф

4
FWIW, я не думаю, что Закон Деметры (он же «наименьшая привилегия») вообще имеет значение. В случае OP его функция не сможет выполнить свою работу, если у нее не будет данных бродяги (потому что это нужно следующему парню из стека вызовов, потому что это нужно следующему, и так далее). Наименее привилегия / Закон Деметры актуальны только в том случае, если параметр действительно не используется , и в этом случае исправление очевидно: удалите неиспользуемый параметр!
Quuxplusone

2
Ситуация в этом вопросе не имеет ничего общего с Законом Деметры ... Существует поверхностное сходство в цепочке вызовов методов, но в остальном это совсем другое.
Эрик Кинг

@Quuxplusone Возможно, хотя в этом случае описание довольно запутанно, потому что цепочечные вызовы не имеют смысла в этом сценарии: вместо этого они должны быть вложенными .
Конрад Рудольф

1
Проблема является очень похожа на нарушения Лода, так как обычно рефакторинг предложил бороться с нарушениями Лода является внедрение данных бродяги. ИМХО, это хорошая отправная точка для уменьшения сцепления, но этого недостаточно.
Йорген Фог

1

Существуют случаи, когда лучше всего (с точки зрения эффективности, удобства обслуживания и простоты реализации) иметь определенные переменные как глобальные, а не накладные расходы на постоянную передачу всего (например, у вас есть около 15 переменных, которые должны сохраняться). Поэтому имеет смысл найти язык программирования, который лучше поддерживает область видимости (как частные статические переменные C ++), чтобы смягчить потенциальный беспорядок (пространства имен и несанкционированного доступа). Конечно, это просто общеизвестно.

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


0

Здесь вообще нет анти-паттерна, потому что вызывающий абонент не знает обо всех этих уровнях ниже и ему все равно.

Кто-то вызывает более высокий уровень (params) и ожидает, что более высокий уровень выполнит свою работу. То, что более высокий уровень делает с params, не относится к числу вызывающих. Более высокий уровень решает проблему наилучшим образом, в этом случае путем передачи параметров на уровень 1 (параметры). Это абсолютно нормально.

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.