Шаблоны для передачи контекста через цепочку методов

19

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

Пример кода, который требует решения

// needs the dependency
function baz(session) {
  session('baz');
}

// doesn't care about the dependency
function bar() {
  baz();
}

// needs the dependency
function foo(session) {
   session('foo')
   bar();
}

// creates the dependency
function start() {
  let session = new Session();
  foo(session);
}

Возможные решения

  • ThreadLocal
  • Глобальный
  • контекстный объект
  • передать зависимость через
  • карри баз и передать его в бар с зависимостью, установленной в качестве первого аргумента
  • внедрение зависимости

Примеры того, где это подходит

Обработка HTTP-запроса

Часто используются объекты контекста в форме атрибутов запроса: см. Expressjs, Java Servlets или .net.

логирование

Для регистрации Java люди часто используют глобалы / синглтоны. Посмотрите типичные шаблоны log4j / commons logging / java logging.

операции

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

Джейми МакКриндл
источник
Пожалуйста, используйте более значимый пример.
Тулаинс Кордова
Я добавил несколько примеров того, как это происходит.
Джейми МакКриндл
3
Я имел в виду более содержательный пример кода.
Тулаинс Кордова

Ответы:

11

Единственный справедливый ответ - это то, что это зависит от идиом вашей программной парадигмы. Если вы используете ОО, почти наверняка неправильно передавать зависимость от метода к методу. Это кодовый запах в ОО. Фактически это одна из проблем, которую решает ОО - объект исправляет контекст. Таким образом, в ОО один правильный (всегда есть другие способы) подход заключается в доставке зависимости через конструктор или свойство. Комментатор упоминает «Внедрение зависимостей», и это совершенно законно, но это не является строго необходимым. Просто предоставьте зависимость, чтобы она была доступна как член fooи baz.

Вы упоминаете карри, поэтому я предполагаю, что о функциональном программировании не может быть и речи. В этом случае философским эквивалентом контекста объекта является замыкание. Любой подход, который, опять же, исправляет зависимость, чтобы она была доступна для зависимых, работает просто отлично. Карри - один из таких подходов (и он заставляет вас звучать умно). Просто помните, что есть и другие способы преодоления зависимости. Некоторые из них элегантны, а некоторые ужасны.

Не забывайте об Аспектно-ориентированном программировании . В последние несколько лет он, похоже, потерял самообладание, но его главная цель - решить именно ту проблему, которую вы описываете. Фактически, классический пример Aspect - это регистрация. В AOP зависимость добавляется автоматически после написания другого кода. Люди АОП называют это " ткачеством ". Общие аспекты вплетены в код в соответствующих местах. Это делает ваш код более легким для размышления и чертовски крутым, но также добавляет новый груз для тестирования. Вам понадобится способ определить, являются ли ваши последние артефакты здоровыми. У AOP есть ответы и на это, так что не пугайтесь.

Скудный Роджер
источник
Утверждение о том, что передача параметров внутри ОО-методов является запахом кода, является весьма спорным утверждением. Я бы сказал совершенно противоположное: поощрение смешивания состояния и функциональности в классе является одной из самых больших ошибок, допущенных в парадигме ОО, и избегание ее путем введения зависимостей непосредственно в методы, а не с помощью конструктора, является признаком хорошо спроектированной части. кода, будь то ОО или нет.
Дэвид Арно
3
@DavidArno Я бы посоветовал использовать другую парадигму и заключить, что состояние объекта является «одной из самых больших ошибок, допущенных ОО-парадигмой», а затем обойти эту парадигму. Я не имею ничего против почти любого подхода, но в целом не люблю код, где автор борется со своим инструментом. Частное государство является отличительной чертой ОО. Если вы отказываетесь от этой функции, вы теряете часть силы ОО.
Скудный Роджер
1
@DavidArno Класс, который имеет все состояние и не имеет никакой функциональности, не имеет механизма для обеспечения инвариантных отношений в состоянии. Такой класс совсем не ОО.
Кевин Крумвиде,
@KevinKrumwiede, в некоторой степени, вы применили Reducto Ad Absudium к моему комментарию, но ваша точка зрения все еще хорошо сформулирована. Инвариантность к государству является важной частью «перехода от ОО». Поэтому, избегая смешивания функциональности и состояния, необходимо обеспечить достаточную функциональность в объекте состояния, необходимую для достижения инвариантности (инкапсулированные поля, устанавливаемые конструктором и доступные через геттеры).
Дэвид Арно
@ScantRoger, я согласен, что может быть принята другая парадигма, а именно функциональная парадигма. Интересно, что большинство современных «ОО» языков имеют растущий список функциональных возможностей, и поэтому можно придерживаться этих языков и принять парадигму функции без «борьбы с инструментом».
Дэвид Арно
10

Если barзависит от того baz, что в свою очередь требует dependency, то barтребует dependencyтоже, чтобы правильно использовать baz. Следовательно, правильными подходами было бы либо передать зависимость в качестве параметра bar, либо карри bazи передать ее bar.

Первый подход проще реализовать и прочитать, но создает связь между barи baz. Второй подход устраняет эту связь, но может привести к менее четкому коду. То, какой подход является наилучшим, вероятно, будет зависеть от сложности и поведения обеих функций. Например, если bazили dependencyимеют побочные эффекты, простота тестирования, вероятно, будет большим драйвером, в котором выбрано решение.

Я бы посоветовал всем другим вариантам, которые вы предлагаете, иметь «хакерскую» природу и, вероятно, привести к проблемам как с тестированием, так и с трудностями выявления ошибок.

Дэвид Арно
источник
1
Я почти полностью согласен. Инъекция зависимости может быть еще одним нехакерским подходом.
Джонатан ван де Веен
1
@JonathanvandeVeen, конечно же, сам процесс передачи dependencyчерез параметры - это внедрение зависимостей?
Дэвид Арно
2
@DavidArno Фреймворки внедрения зависимостей не избавляются от таких зависимостей, они просто перемещают их. Магия в том, что они перемещают их за пределы ваших классов, в место, где тестирование является проблемой чьего-то другого.
Кевин Крумвиде,
@JonathanvandeVeen Я согласен, внедрение зависимостей является правильным решением. На самом деле это тот, который я бы выбрал чаще всего.
Джейми МакКриндл
1

Философски говоря

Я согласен с беспокойством Дэвида Арно .

Я читаю ОП как поиск решений для реализации. Тем не менее, ответ - изменить дизайн . «Узоры»? ОО дизайн, можно сказать, все о контексте. Это огромный чистый лист бумаги, наполненный возможностями.

Работа с существующим кодом - это другой, ну, контекст.



Сейчас я работаю над той же проблемой. Ну, я исправляю сотни строк кода copy-n-paste, которые были сделаны только для того, чтобы можно было ввести значение.

Модульный код

Я выбросил 600 строк повторяющегося кода, а затем рефакторинг, поэтому вместо «А звонки Б звонки С звонки D ...» у меня есть «Звонок А, возврат, Звонок Б, возврат, Звонок С ...». Теперь нам нужно только ввести значение в один из этих методов, скажем, метод E.

Добавьте параметр по умолчанию в конструктор. Существующие абоненты не меняются - здесь указывается слово «по желанию». Если аргумент не передан, используется значение по умолчанию. Затем изменяется только 1 строка, чтобы передать переменную в измененную модульную структуру; и небольшое изменение в методе Е, чтобы использовать его.


Затворы

Поток программистов - "Почему программа использует замыкание?"

По сути, вы вводите значения в метод, который возвращает метод, настроенный со значениями. Этот настроенный метод впоследствии выполняется.

Этот метод позволит вам изменить существующий метод без изменения его сигнатуры.

radarbob
источник
Этот подход выглядит странно знакомым ...
Роджер по вопросу временной связи (вашей ссылки), @Snowman. Важно, чтобы требуемый порядок выполнения был инкапсулирован.
Радар Боб