C++ 基于接口的C+编程+;与迭代器结合使用。如何保持这个简单?
在我的开发过程中,我正慢慢地从面向对象的方法转向基于接口的编程方法。更准确地说:C++ 基于接口的C+编程+;与迭代器结合使用。如何保持这个简单?,c++,design-patterns,interface,iterator,C++,Design Patterns,Interface,Iterator,在我的开发过程中,我正慢慢地从面向对象的方法转向基于接口的编程方法。更准确地说: 过去,如果我能将逻辑分组到一个类中,我已经很满意了 现在我倾向于在接口后面添加更多逻辑,让工厂创建实现 一个简单的例子说明了这一点 过去我写过这些课程: 图书馆 书 现在我写这些类: 图书馆 图书馆 图书馆工厂 IBook 书 书厂 这种方法允许我为我的每个接口轻松地实现模拟类,在旧的、较慢的实现和新的、较快的实现之间切换,并在同一个应用程序中比较它们 在大多数情况下,这非常有效,但如果我想使用迭代器在
- 过去,如果我能将逻辑分组到一个类中,我已经很满意了
- 现在我倾向于在接口后面添加更多逻辑,让工厂创建实现
- 图书馆
- 书
- 图书馆
- 图书馆
- 图书馆工厂
- IBook
- 书
- 书厂
for (Library::iterator it=myLibrary.begin();it!=mylibrary.end();++it) ...
ILibraryBookFinder *myFinder = LibraryBookFinderFactory (FINDER_POPULARITY);
for (ILibraryBookFinder::const_iterator it=myFinder->begin();it!=myFinder.end();++it) ...
问题是,在基于接口的方法中,不能保证不同的ILibrary实现使用相同类型的迭代器。如果OldLibrary和NewLibrary都继承自ILibrary,则:
- OldLibrary可以使用std::vector来存储其书籍,并在其begin和end方法中返回std::vector::const_迭代器
- NewLibrary可以使用std::list来存储其书籍,并在其begin和end方法中返回std::list::const_迭代器
这意味着在实践中,我必须使迭代器也成为接口,这意味着应用程序不能将迭代器放在堆栈上(典型的C++切片问题)。 我可以通过将迭代器接口封装在一个非接口类中来解决这个问题,但对于我试图实现的内容来说,这似乎是一个相当复杂的解决方案
有没有更好的方法来处理这个问题 编辑: 马丁发言后的一些澄清 假设我有一个类返回所有按流行程度排序的书籍:LibraryBookFinder。 它有begin()和end()方法,返回引用书籍的LibraryBookFinder::const_迭代器 要用全新的实现替换旧的实现,我想将旧的LibraryBookFinder放在接口ILibraryBookFinder后面,并将旧的实现重命名为OldSlowLibraryBookFinder 然后,我的新(快速膨胀的)实现VeryFastCachingLibraryBookFinder可以从ILibraryBookFinder继承。这就是迭代器问题的来源 下一步可能是将界面隐藏在工厂后面,在那里我可以问工厂“给我一个‘查找器’,它非常擅长根据受欢迎程度、标题或作者……返回书籍。您最终得到的代码如下:for (Library::iterator it=myLibrary.begin();it!=mylibrary.end();++it) ...
ILibraryBookFinder *myFinder = LibraryBookFinderFactory (FINDER_POPULARITY);
for (ILibraryBookFinder::const_iterator it=myFinder->begin();it!=myFinder.end();++it) ...
或者如果我想使用其他标准:
ILibraryBookFinder *myFinder = LibraryBookFinderFactory (FINDER_AUTHOR);
for (ILibraryBookFinder::const_iterator it=myFinder->begin();it!=myFinder.end();++it) ...
LibraryBookFinderFactory的参数可能由外部因素决定:配置设置、命令行选项、对话框中的选择等等,每个实现都有自己的优化(例如,一本书的作者不会改变,所以这可能是一个相当静态的缓存;流行度每天都可以改变,这可能意味着一个完全不同的数据结构)。 如果你的图书馆拥有大量的书,你应该考虑把你的“聚合”函数放进你的集合中,并在动作中传递它要执行的东西。 具有以下性质的事物:
class ILibrary
{
public:
virtual ~Ilibrary();
virtual void for_each( boost::function1<void, IBook> func ) = 0;
};
LibraryImpl::for_each( boost::function1<void, IBook> func )
{
std::for_each( myImplCollection.begin(), myImplCollection.end(), func );
}
class-ILibrary
{
公众:
虚拟~Ilibrary();
每个函数的虚拟空(boost::function1 func)=0;
};
LibraryImpl::for_each(boost::function1 func)
{
std::for_each(myImplCollection.begin()、myImplCollection.end()、func);
}
虽然可能不完全是这样,因为您可能需要使用共享的ptr、constness等。出于这个目的(或者通常在我大量使用接口的实现中),我还为迭代器创建了一个接口,其他对象返回这个接口。它变得非常类似Java
如果您关心在大多数情况下使用迭代器:您的问题当然是您在编译时不知道迭代器的大小,因此无法分配正确大小的堆栈变量。但是如果您确实非常关心这一点:也许您可以编写一些包装器,在sta上分配特定大小ck(例如128字节),如果新的迭代器适合,它会将其移动到那里(确保迭代器有一个正确的接口,以干净的方式允许此操作)。或者您可以使用alloca()
。例如,您的迭代器接口可能类似于:
struct IIterator {
// iterator stuff here
// ---
// now for the handling on the stack
virtual size_t size() = 0; // must return own size
virtual void copyTo(IIterator* pt) = 0;
};
还有你的包装纸:
struct IteratorWrapper {
IIterator* pt;
IteratorWrapper(IIterator* i) {
pt = alloca(i->size());
i->copyTo(pt);
}
// ...
};
大概吧
另一种方法是,如果理论上编译时总是很清楚(不确定这是否适用;这是一个明确的限制):在任何地方都使用函子。这有许多其他缺点(主要是在头文件中包含所有真实代码),但您的代码速度非常快。例如:
template<typename T>
do_sth_with_library(T& library) {
for(typename T::iterator i = library.begin(); i != library.end(); ++i)
// ...
}
模板
用图书馆做某事(T&library){
for(typename T::iterator i=library.begin();i!=library.end();++i)
// ...
}
但是如果你过于依赖它,代码可能会变得非常难看
CashCow提供了另一个很好的解决方案(使代码更加实用——为每个接口实现
)
使用当前C++,这会使代码使用起来有点复杂/难看。随着即将到来的C++ 0x和lambda函数,这个解决方案会变得更加干净。