Плюсы и минусы инверсии контроля - PullRequest
22 голосов
/ 01 марта 2011

Предположим, у меня есть поток объектов [acme], которые я хочу представить через API.У меня есть два варианта: обратные вызовы и итераторы.

API # 1: обратные вызовы

// API #1
// This function takes a user-defined callback 
// and invokes it for each object in the stream.
template<typename CallbackFunctor>
void ProcessAcmeStream(CallbackFunctor &callback);

API # 2: итераторы

// API #2
// Provides the iterator class AcmeStreamIterator.
AcmeStreamIterator my_stream_begin = AcmeStreamIterator::begin();
AcmeStreamIterator my_stream_end   = AcmeStreamIterator::end();

API # 1 принимает поток управленияпрограммы из руки пользователя и не вернется, пока не будет использован весь поток (на данный момент не обращая внимания на исключения).

API # 2 сохраняет поток управления в руке пользователя, позволяя пользователю двигаться впередсам по себе.

API # 1 чувствует себя более высокого уровня , позволяя пользователям сразу перейти к бизнес-логике (функтор обратного вызова).С другой стороны, API № 2 выглядит более гибким, позволяя пользователям более низкий уровень контроля.

С точки зрения дизайна, какой вариант выбрать?Есть ли еще плюсы и минусы, которые я еще не видел?Какие проблемы с поддержкой / обслуживанием на будущее?

Ответы [ 5 ]

9 голосов
/ 01 марта 2011

Итераторский подход является более гибким, поскольку версия обратного вызова легко реализуется в терминах первого с помощью существующих алгоритмов:

std::for_each( MyStream::begin(), MyStream::end(), callback );
6 голосов
/ 01 марта 2011

ИМО, второй явно выше. Хотя я могу (вроде) понять ваше чувство, что это более низкий уровень, я думаю, что это неправильно. Первая определяет свою собственную конкретную идею «более высокого уровня», но она не вписывается в остальную часть стандартной библиотеки C ++ и в итоге становится относительно трудной в использовании. В частности, требуется, чтобы, если пользователь хочет что-то, эквивалентное стандартному алгоритму, его необходимо заново реализовать с нуля, а не с использованием существующего кода.

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

5 голосов
/ 01 марта 2011

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

Отмена перечисления легко сделать с помощью обратного вызова, кстати: просто позвольте обратному вызову вернуть bool и продолжать только до тех пор, пока он возвращает true.

4 голосов
/ 01 марта 2011

C ++ стандартная библиотека идиома для обеспечения итераторов. Если вы предоставляете итераторы, то ProcessAcmeStream - это простая оболочка для std::for_each. Может быть, стоит написать, а может и нет, но это не совсем продвигает вашего абонента в радикально новый мир юзабилити, это новое имя для применения стандартной библиотечной функции к вашей паре итераторов.

В C ++ 0x, если вы также сделаете пару итераторов доступной через std::begin и std::end, тогда вызывающая сторона может использовать для диапазона диапазон, который переносит их в бизнес-логику так же быстро, как ProcessAcmeStream, возможно быстрее.

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

2 голосов
/ 01 марта 2011

С точки зрения дизайна, я бы сказал, что метод итератор лучше, просто потому, что он проще, а также более гибок; очень неприятно делать функции обратного вызова без лямбды. (Теперь, когда C ++ 0x будет иметь лямбда-выражения, это может стать менее важным, но даже при этом метод итератора является более универсальным.)

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

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

...