重读COM本质的体会--第一章

来源:互联网 发布:安广网络怎么样 编辑:程序博客网 时间:2024/04/28 14:57

1 接口与实现相分离:

原因

接口是供外部调用者使用的,一般来说不要随便修改接口,原因是:1 使得外部调用者需要改代码,当调用层代码量很大的时候,可能会漏掉一些地方,使得出现未知错误。2 调用者必须重新编译程序,对于自动升级的程序来说需要替换很多部分,管理带来麻烦。等等很多原因, 我们希望我们的新DLL扔进程序就可以用。我们的实现部分有很多BUG,在维护过程中需要不断的修改程序,DLL也不断升级,只要把新DLL扔进程序就能解决用户的问题。 为了解决这个问题就要求接口与实现相分离。

解决方法:

在软件设计初,先定义好接口部分,而不需要考虑实现部分,接口对调用者来说要方便,清晰,方便使用,考虑要周全,尽量考虑前期和中期开发需要,哪怕实现部分可以暂时空下。当实现部分无论怎么变化,只要接口不变,那么DLL可以丢入程序就能用。

 

那么接口该定义成什么样子呢?

1 使用__stdcall 引出 、

_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。

避免一些编译器的不同的编译方式导致调用问题。

 

2 使用纯虚类做接口,里面不包含任何成员变量, 也是为的是编译器的无关行。 具体的原因有无本人水平问题,也不是很理解清楚。

 

3 怎样创建这个对象呢? 使用extern "C"的方式引出创建一个类成员的函数 

exern "C" __stdcall 接口类 Createxxxx(xxx)

实现为

实现类 xx = new 实现类;

return xx; 

 

4 销毁实例,

如果使用delete Createxxxx(xxx), 会导致内存泄露, 使用虚析构可以解决,但是避免编译器相关性(不是很理解, 本人只会 VC), 使用接口函数来释放

extern "C" __stdcall Destroyxxx(接口类 *xx)

....

 

使用  extern "C" 引出是为了编译器对函数名的改变方式不同导致找不到函数,本人推荐使用DEF文件导出

 

5 运行时多态:

对于一个接口在不同的情况下对应某个特定的实现,那么各个不同的实现可以写成不同的DLL,在Createxxxx(xxx) 的时候选择调用某个DLL, 创建该类的实例, 有一点要说明的是这些DLL的实现类都要继承该虚接口, 这些DLL可以使用Createxxx创建该类的实例, 这样方便对实现的扩展。

 

6 对象的扩展性

上面有个假设,我们的虚接口不需要变换, 但是实际中需要添加一些新功能,那么可以在抽象接口类中加入新的方法, 实现扩展, 这样存在这么一个问题, 当一个使用了这些新的功能的应用层的软件混入了一个旧的DLL的时候,那么程序肯定Crash,那么怎么样解决这个版本问题呢?

1 DLL后带版本号, MFC就是这么做的, 导致版本过多不要维护,很糟糕

2 定义一个新的接口,继承老的接口, 使用dynamic_cast判断输入的类是否与新接口相容,也就是说,如果这个对象是老的DLL生成的实例,那么使用dynamic_cast<新接口 *>(xx) 返回NULL,这种方式去判断

, 由调用者去把握。但是dynamic_cast是编译器相关的。

3 考虑到正交性, 一个实现类暴露出2个以及以上的虚接口, 实现类只需要继承这些虚接口就可以了, 在后续升级中,可能要添加一些方法, 添加一些正交的虚接口, 那么为了解决老DLL的问题,我们需要查询该实例是否支持某些虚接口, 为了避免dynamic_cast是编译器相关性,我们添加 作用与dynamic_cast一样的查询接口的函数 QueryInterface的函数,查看该实例是否支持该某些接口。

 

7 资源管理,也就是引用计数。

 

8 为什么有IUNKnown接口, 现在一个实现有了很多抽象接口,而查询某个对象是否支持某个抽象接口类,还有引用计数的维护,都是公共部分,因此设计出一个IUNKown接口,所有的抽象接口都继承IUNKown.....

 

架构的设计不一定要设计成COM+的样子, 但是理解了COM的设计由来,对设计接口是非常有帮助的。