Подтвердить что ты не робот

Плюсы и минусы инверсии контроля

Предположим, что у меня есть поток объектов [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 чувствует себя более гибким, что позволяет пользователям более низкого уровня управления.

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

std::for_each( MyStream::begin(), MyStream::end(), callback );

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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