本文共 3084 字,大约阅读时间需要 10 分钟。
在设计一个SDK或一个可重用的框架时,设计者必然要考虑的一个问题就是实现如何不同版本的兼容,即如何保证基于低版本开发的应用程序不需要作任何改动(如重新编译)就可以运行的在高版本的SDK或框架下。本文提出了VC++下实现接口兼容的三种不同的方式。
关键词 C++,设计,接口,框架,VC++
在设计一个SDK或一个可重用的框架时,设计者必然要考虑的一个问题就是实现如何不同版本的兼容,即如何保证基于低版本开发的应用程序不需要作任何改动(如重新编译)就可以运行的在高版本的SDK或框架下。因此,我们需要在架构/设计SDK或框架时,根据使用的设计语言(如C++、C#、Java)以及SDK或框架的发布粒度(如接口)来选择合适的设计方案以及实现方式。当然,有时候我们还需要综合考虑效率、可用性等影响框架设计的关键因素。本文探索了使用VC++设计的、以接口为发布粒度的SDK或框架时,在接口的兼容性方面需要考虑的一些基本问题。
C++标准并没有对对象模型做强制性的规定,每个编译器都可以有自己的实现方式,事实上VC++采用的对象模型基本同《C++对象模型》所描述的对象模型。如果需要对VC++的对象模型有比较深入的了解,可以参考《C++对象模型》。
接口的概念在软件设计的不同年代有不同的描述,如接口代表了契约、接口分离了抽象和实现等。因此本文对接口的概念不做定义和引用。从语法的角度上来看,VC++中的接口就是一个类(class)或结构(struct),而这个类(class)或结构(struct) 中只有纯虚的成员函数,没有任何成员变量。如:
interface IContract{
virtual void Sign() = 0;
virtual void GetContent() const = 0;
………………………………………..
};
在这节中,我们通过一个示例来陈述如何在不同版本之间实现接口的二进制兼容。
假如我们开发了一个软件XML Writer,用于编辑特定格式的XML文件(如CML),我们决定采用小核心加插件的架构形式来实现这个软件,所以我们需要一个SDK提供给插件开发者。我们决定采用测试驱动开发的方式来实现我们的SDK,因为这样可以保证最终SDK的可用性、可测试性。
在版本1中我们计划发布了下面的这么一个接口:
//这个接口供插件调用来操作整个文档
interface IDocument
{
virtual void Close() = 0;
virtual void Export(const string& filename) const = 0;
};
插件开发者获取接口的方式为:IDocument* GetActiveDocument();
在版本2中,我们接到插件开发者的需求需要添加在IDocument添加一个接口函数用于获取文档路径(GetFileName())。
不同版本之间的接口兼容是我们必须要考虑的问题。接口兼容设计的好坏直接影响到了以后对于SDK的维护、升级,甚至还会影响到SDK的可用性。
在VC++中,只要不改变已有接口函数的参数以及不同接口函数之间的顺序, 我们就可以保证不同版本之间的接口的兼容。
这种方式不但可以实现接口的向后兼容,而且还保证了接口的可用性。但是这种方式最大的缺点就是,我们必须依赖于VC++的编译器,因为很多编译器对于接口中的函数不是按照顺序进行编译的。但是这在VC2003和VC2005是安全的。
那么版本2的IDocument接口如下:
interface IDocument
{
virtual void Close() = 0;
virtual void Export(const string& filename) const = 0;
virtual string GetFileName() const = 0;
};
插件开发者获取接口的方式不变,为:IDocument* GetActiveDocument();
我们可以因添加接口的形式来提供。这种方式在不同的编译器之间都是安全的,但是这种方式的缺点是,因为增加一个接口给后续开发者带来了不必要的复杂性,从而降低了接口的可用性。
那么版本2的接口为:
interface IDocument
{
virtual void Close() = 0;
virtual void Export(const string& filename) const = 0;
};
//新增的接口
interface IDocument2
{
virtual string GetFileName() const = 0;
};
插件开发者获取接口的方式为:IDocument* GetActiveDocument();
获取IDocument2的方式为:IDocument2* p = GetActiveDocument()->XXX();
上面两种方式多时存在缺陷的。那么我们是否可以综合上面两种方式,去弊存利,在接口的兼容和可用性方面得到很好的平衡呢?我们可以有两种方式:
方式A: 使用接口继承。尽管这种方式有很大的优点,但是对于接口的可用性并没有做质的改变。
那么版本2的接口为:
interface IDocument
{
virtual void Close() = 0;
virtual void Export(const string& filename) const = 0;
};
//新增的接口
interface IDocument2 : public IDocument
{
virtual string GetFileName() const = 0;
};
插件开发者获取接口的方式为:IDocument* GetActiveDocument();
获取IDocument2的方式为:IDocument2* p = GetActiveDocument()->XXX();
方式B:也使用继承,但是提供给开发者的接口为一个抽象类。这种方式平衡了接口兼容和接口可用性。但是实现起来比较枯燥。但是如果给接口的使用者带来快乐是最重要的。但是这种机制需要我们在发布接口之前就考虑的。
版本1的接口为:
interface IDocument
{
virtual void Close() = 0;
virtual void Export(const string& filename) const = 0;
};
class Document : IDocument{…};
插件开发者获取接口的方式为:Document * GetActiveDocument();
版本2的接口为:
interface IDocument
{
virtual void Close() = 0;
virtual void Export(const string& filename) const = 0;
};
//新增的接口
interface IDocument2 : public IDocument
{
virtual string GetFileName() const = 0;
};
class Document : public IDocument, public IDocument2
插件开发者获取接口的方式不变,为:Document * GetActiveDocument();
转载地址:http://xthpi.baihongyu.com/