Есть ли способ справиться с вышеупомянутой ситуацией?
Похоже, foo()
нужна какая-то информация о контексте, в котором он работает, чтобы сделать правильную вещь,Если вы думаете об этом таким образом, то этот контекст, каким бы он ни был, явно является входным значением для foo()
, и поэтому должен быть передан как параметр.Другими словами, вы должны назвать это чем-то вроде foo(context)
.
Или здесь есть недостаток дизайна?
Это зависит от обстоятельств.Функция на самом деле не должна знать или беспокоиться о том, откуда она вызывается, но, возможно, есть некоторая информация, относящаяся к вызывающей стороне, которая имеет смысл в качестве ввода.Например, если вы знаете, что bar()
вызывается только тогда, когда доступны некоторые данные, а tar()
вызывается только тогда, когда данные недоступны, вы можете передать параметр, который указывает, доступны ли данные.Или вы можете просто передать сами данные, если они доступны, или nil
, если это не так.
Причина, по которой функция не должна заботиться о том, кто ее вызывает, заключается в том, что такое знание делает функцию хрупкой., трудно проверить, и трудно использовать.Это хрупко, потому что функция может сломаться, если обстоятельства вызывающего абонента изменятся.Это сложно протестировать, потому что тестирование обычно включает вызов функции, и ожидается, что поведение функции будет одинаковым независимо от того, откуда она вызывается.И его трудно использовать, потому что вам нужно знать о зависимости функции от вызывающего: если вы когда-нибудь захотите вызвать ее откуда-то еще, вам нужно обновить функцию.
Бывают случаи, когда bar() & tar () не вызывают напрямую foo (), т.е. между ними много вызовов, но решение должно быть принято в bar () и tar ().
Ответ до сих порпочти то же самое: вызывающая сторона имеет некоторую информацию, которая вводится вызываемой стороне, поэтому она должна передать ее в качестве параметра.Рассмотрим цепочку вызовов:
tar () -> func1 () -> func2 () -> foo ()
, так как tar()
вызывает func1()
вместо foo()
он не должен делать никаких предположений о том, как работает func1()
- он должен просто передавать информацию, которая необходима func1()
для выполнения своей работы, и возвращать результат, который возвращает func1()
.«Решение», которое tar()
и bar()
принимают, каким бы оно ни было на самом деле, является для вас необходимым входным значением для foo()
, и, следовательно, необходимым входным значением для func2()
, и, следовательно, необходимым вкладом для func1()
, даже если func1()
и func2()
не используют эту информацию, кроме как для вызова какой-либо другой функции.Таким образом, один из вариантов состоит в том, чтобы каждая промежуточная функция принимала новый параметр, который они просто передают следующей функции в цепочке.
Другой вариант, который может работать лучше, если может быть более одного «решения»это влияет на foo()
, состоит в том, чтобы создать некую структуру, которая предоставляет информацию «контекста» или «среды» и имеет промежуточные функции, каждая из которых передает ее.Вы часто видите этот стиль в графических системах, где есть контекст рисования с большим количеством параметров, которые могут быть изменены, и этот контекст является параметром, передаваемым большинству функций в системе.
Проблема, с которой вы столкнулисьперед нами процедурная версия проблемы «как получить нужные мне данные», которая часто возникает в объектно-ориентированном программировании.Возможны следующие варианты: 1) сделать так, чтобы объект предположил, где взять необходимые ему данные;или 2) сообщить объекту, где взять необходимые ему данные.Вариант 1 обычно включает одноэлементную или другую глобально доступную порцию данных.Вариант 2 известен как внедрение зависимостей , и его часто называют «скажи, не спрашивай».
Другое решение, которое вы, возможно, не рассматривали, - это разбить foo()
на две или более функции, чтобы tar()
и bar()
в итоге вызывали две разные функции, каждая из которых выполняет свою задачу в нужном направлении.Это может сработать, если цепочки вызовов для tar()
и bar()
действительно различны.В этом случае вы по-прежнему фактически передаете ту же информацию - вы просто делаете это неявно, используя имя функции.
Давайте попробуем конкретизировать этот совет, посмотрев предоставленный вами код:
void foo()
{
if (!IN_BAR)
data = calculate();
// Use data in this method.
printf("Data: %d\n", data);
}
Причина, по которой foo()
здесь зависит от IN_BAR
, заключается в том, что bar()
вызывает calculate()
и сохраняет результат в data
, который является другой глобальной переменной.Если foo()
вызывается как часть tar()
, то calculate()
не был вызван и data
, вероятно, не будет иметь полезного значения, поэтому вам нужно вызвать его внутри foo()
в этом случае.
Ваш конкретный вопрос, похоже, касается способов избежать необходимости IN_BAR
, поэтому давайте сначала рассмотрим это.Одним из решений здесь является просто сделать действительное data
требование для вызова foo()
.Если вы сделаете это, тогда foo()
не нужно IN_BAR
- он может просто предположить, что data
всегда допустимо, потому что вызов его без этого является ошибкой.Он снимает с себя ответственность за вызов calculate()
из сферы foo()
и позволяет вам звонить calculate()
куда угодно выше по цепочке - вы можете сделать это в tar()
или func_tar()
или в любом другом месте,до тех пор, пока вы делаете это до вызова foo()
, а foo()
становится проще:
void foo()
{
// Use data in this method.
printf("Data: %d\n", data);
}
Если есть какая-то причина, по которой вы не можете этого сделать, то вы можете, по крайней мере, связать действительностьdata
к этой одной переменной.Вы можете сделать это, либо определив какое-либо значение для data
, что означает «недействительный», так что вы можете просто просмотреть данные и узнать, действительно ли это допустимо.Например, вы могли бы изменить его тип на int*
и инициализировать его на nil
, а затем calculate()
вернуть указатель на int
, который он вычисляет:
void foo()
{
if (data == nil)
data = calculate();
// Use data in this method.
printf("Data: %d\n", data);
}
Это будетгораздо лучше, однако, переопределить foo()
, чтобы data
передавался явно:
void foo(int data)
{
// Use data in this method.
printf("Data: %d\n", data);
}
Теперь вам не нужны ни data
, ни IN_BAR
как глобальные, а foo()
Потребность в значении для data
очевидна.Если для foo()
вполне нормально вызывать calculate()
, когда нет значения data
, то, безусловно, должно быть в порядке, чтобы func_tar()
вызывал calculate()
непосредственно перед вызовом foo()
:
void func_tar()
{
foo( calculate() );
}
В случае bar()
, который вызывает сам calculate()
, у вас уже есть значение data
, и оно должно быть передано его вспомогательным функциям:
void func_bar(int data)
{
foo(data);
}
Это правдадаже если func_bar()
заменяет несколько функций. Суть в том, что использование глобальной переменной, чтобы избежать передачи data
информации в параметрах, является плохой идеей. Возможно, data
также является заменой для нескольких различных значений;в этом случае объедините их все в одну структуру, как я описал в обсуждении выше.