控制反转的优点和缺点
假设我有一个 [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 感觉更灵活,允许用户进行较低级别的控制。
从设计的角度来看,我应该选择哪一个?还有更多我还没有看到的优点和缺点吗?未来有哪些支持/维护问题?
Suppose I have a stream of [acme] objects that I want to expose via an API. I have two choices, callbacks and iterators.
API #1: Callbacks
// 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: Iterators
// API #2
// Provides the iterator class AcmeStreamIterator.
AcmeStreamIterator my_stream_begin = AcmeStreamIterator::begin();
AcmeStreamIterator my_stream_end = AcmeStreamIterator::end();
API #1 takes the control flow of the program from the user's hand and will not return until the entire stream is consumed (forgetting exceptions for the moment).
API #2 retains the control flow in the user's hand, allowing the user to move forward the stream on his own.
API #1 feels more higher level, allowing the users to jump to the business logic (the callback functor) right away. On the other hand, API #2 feels more flexible, allowing the users lower-level of control.
From a design perspective, which one should I go with? Are there more pros and cons that I have not seen yet? What are some support/maintenance issues down the future?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
迭代器方法更加灵活,回调版本可以通过现有算法轻松实现第一个版本:
The iterator approach is more flexible, with the callback version being easily implemented in terms of the first one by means of existing algorithms:
IMO,第二个显然更胜一筹。虽然我(在某种程度上)可以理解你认为它的水平较低的感觉,但我认为这是不正确的。第一个定义了它自己的“更高级别”的特定概念 - 但它与 C++ 标准库的其余部分不太适合,并且最终相对难以使用。特别是,它要求如果用户想要相当于标准算法的东西,则必须从头开始重新实现,而不是使用现有代码。
第二个与库的其余部分完美契合(假设您正确实现了迭代器),并为用户提供了通过标准算法(和/或新的、遵循标准模式的非标准算法)。
IMO, the second is clearly superior. While I can (sort of) understand your feeling that it's lower level, I think that's incorrect. The first defines its own specific idea of "higher level" -- but it's one that doesn't fit well with the rest of the C++ standard library, and ends up being relatively difficult to use. In particular, it requires that if the user wants something equivalent to a standard algorithm, it has to be re-implemented from the ground up rather than using existing code.
The second fits perfectly with the rest of the library (assuming you implement your iterators correctly) and gives the user an opportunity for dealing with your data at a much higher level via standard algorithms (and/or new, non-standard algorithms that follow the standard patterns).
与迭代器相比,回调的一个优点是 API 的用户不会搞乱迭代。很容易比较错误的迭代器,或者使用错误的比较操作,或者以其他方式失败。回调 API 可以防止这种情况发生。
使用回调可以轻松取消枚举,顺便说一句:只需让回调返回
bool
并仅在返回true
时继续。One advantage of callbacks over iterators is that users of your API can't mess up iteration. It's easy to compare the wrong iterators, or use the wrong comparison operation or fail in some other way. The callback API prevents that.
Canceling enumeration is easily done using a callback, BTW: Just let the callback return a
bool
and continue only as long as it returnstrue
.C++标准库的习惯用法是提供迭代器。如果您提供迭代器,则
ProcessAcmeStream
是std::for_each
的简单包装器。也许值得编写,也许不值得,但它并不完全将您的调用者提升到一个全新的可用性世界,它是标准库函数到迭代器对的应用程序的新名称。在 C++0x 中,如果您还通过
std::begin
和std::end
使迭代器对可用,那么调用者可以使用基于范围的 for,这将使用它们与 ProcessAcmeStream 一样快地融入业务逻辑,甚至更快。所以我想说,如果可以提供迭代器,那么就提供它 - 如果调用者想要以这种方式编程,C++ 标准会为您执行控制反转。至少,对于控制如此简单的情况来说是这样。
C++ standard library idiom is to provide iterators. If you provide iterators, then
ProcessAcmeStream
is a simple wrapper aroundstd::for_each
. Maybe worth the trouble of writing, maybe not, but it isn't exactly boosting your caller into a radical new world of usability, it's a new name for an application of a standard library function to your iterator pair.In C++0x, if you also make the iterator pair available through
std::begin
andstd::end
then caller can use range-based for, which takes them into the business logic just as quickly asProcessAcmeStream
does, perhaps quicker.So I'd say, if it's possible to provide an iterator then provide it - the C++ standard does inversion of control for you if the caller wants to program that way. At least, for a case where the control is as simple as this it does.
从设计的角度来看,我认为迭代器方法更好,因为它更简单,也更灵活;在没有 lambda 的情况下创建回调函数真的很烦人。 (现在 C++0x 将具有 lambda 表达式,尽管如此,这可能不再是一个问题,但即便如此,迭代器方法也更加通用。)
回调的另一个问题是取消。你可以返回一个布尔值来指示是否要取消枚举,但是当控制权不在我手中时我总是感到不安,因为你并不总是知道会发生什么。迭代器不存在这个问题。
当然,始终存在这样的问题:迭代器可以随机访问,而回调则不能,因此它们也更具可扩展性。
From a design perspective, I would say that the iterator method is better, simply because it's easier and also more flexible; it's really annoying to make callback functions for without lambdas. (Now that C++0x will have lambda expressions, though, this may become less of a concern, but even still, the iterator method is more generic.)
Another issue with callbacks is cancellation. You can return a boolean value to indicate whether you'd like to cancel enumeration, but I always feel uneasy when the control is out of my hands, since you don't always know what might happen. Iterators don't have this issue.
And of course, there's always the issue that iterators can be random-access whereas callbacks aren't, so they're more extensible as well.