C++中基于接口的编程与迭代器的结合.怎么这么简单?

Pat*_*ick 9 c++ iterator design-patterns interface

在我的开发中,我正在慢慢地从面向对象的方法转向基于接口的编程方法.更确切地说:

  • 在过去,如果我能在课堂上分组逻辑,我已经满意了
  • 现在我倾向于在界面后面添加更多逻辑并让工厂创建实现

一个简单的例子澄清了这一点.

过去我写过这些课程:

  • 图书馆

现在我写这些类:

  • ILibrary
  • 图书馆
  • LibraryFactory
  • 我订了
  • BookFactory

这种方法允许我轻松地为每个接口实现模拟类,并在旧的,较慢的实现和新的,更快的实现之间切换,并在同一个应用程序中进行比较.

对于大多数情况,这非常好,但如果我想使用迭代器来循环集合,它就会成为一个问题.

假设我的图书馆有一系列书籍,我想对它们进行迭代.在过去,这不是问题:Library :: begin()和Library :: end()返回一个迭代器(Library :: iterator),我可以在其上轻松编写循环,如下所示:

for (Library::iterator it=myLibrary.begin();it!=mylibrary.end();++it) ...
Run Code Online (Sandbox Code Playgroud)

问题是在基于接口的方法中,不能保证ILibrary的不同实现使用相同类型的迭代器.如果例如OldLibrary和NewLibrary都继承自ILibrary,那么:

  • OldLibrary可以使用std :: vector来存储它的书籍,并在其开始和结束方法中返回std :: vector :: const_iterator
  • NewLibrary可以使用std :: list来存储它的书籍,并在其开始和结束方法中返回std :: list :: const_iterator

要求两个ILibrary实现都返回相同类型的迭代器也不是解决方案,因为在实践中,增量操作(++ it)需要在两个实现中以不同方式实现.

这意味着在实践中我必须使迭代器成为一个接口,这意味着应用程序不能将迭代器放在堆栈上(典型的C++切片问题).

我可以通过在非接口类中包装iterator接口来解决这个问题,但对于我尝试obtian的东西,这似乎是一个非常复杂的解决方案.

有没有更好的方法来处理这个问题?

编辑: 马丁发表评论后的一些澄清.

假设我有一个类,它返回按受欢迎程度排序的所有书籍:LibraryBookFinder.它有begin()和end()方法,它们返回一个引用书籍的LibraryBookFinder :: const_iterator.

要用一个全新的实现替换旧的实现,我想将旧的LibraryBookFinder放在ILibraryBookFinder接口后面,并将旧实现重命名为OldSlowLibraryBookFinder.

然后我的名为VeryFastCachingLibraryBookFinder的新(快速快速)实现可以继承ILibraryBookFinder.这是迭代器问题的来源.

下一步可能是隐藏工厂后面的界面,在那里我可以要求工厂"给我一个'发现者'非常擅长根据人气,或根据标题,或作者,...返回书籍.你最终得到了像这样的代码:

ILibraryBookFinder *myFinder = LibraryBookFinderFactory (FINDER_POPULARITY);
for (ILibraryBookFinder::const_iterator it=myFinder->begin();it!=myFinder.end();++it) ...
Run Code Online (Sandbox Code Playgroud)

或者如果我想使用其他标准:

ILibraryBookFinder *myFinder = LibraryBookFinderFactory (FINDER_AUTHOR);
for (ILibraryBookFinder::const_iterator it=myFinder->begin();it!=myFinder.end();++it) ...
Run Code Online (Sandbox Code Playgroud)

LibraryBookFinderFactory的参数可以由外部因素决定:配置设置,命令行选项,对话框中的选择,......每个实现都有自己的优化(例如,书籍的作者不会改变)所以这可以是一个非常静态的缓存;流行度可以每天改变,这可能意味着完全不同的数据结构).

Mar*_*ork 3

你在这里混合了隐喻。

如果库是一个容器,那么它需要自己的迭代器,它不能重用成员的迭代器。因此,您可以将成员迭代器包装在 ILibraryIterator 的实现中。

但严格来说,图书馆不是一个容器,而是一个图书馆。
因此,库上的方法是您可以在库上执行的操作(此处考虑动词)。库可能包含容器,但严格来说它不是容器,因此不应公开 begin() 和 end()。

因此,如果您想对书籍执行操作,您应该要求图书馆执行该操作(通过提供函子)。类的概念是它是自包含的。用户不应该使用 getter 来获取有关对象的内容,然后将内容放回对象应该知道如何对其自身执行操作(这就是为什么我讨厌 getter/setter,因为它们破坏了封装)。

class ILibrary
{
    public:
         IBook const& getBook(Index i) const;

         template<R,A>
         R checkBooks(A const& librarianAction);
};
Run Code Online (Sandbox Code Playgroud)