1. 概述
在模块化越来越规范的现代软件开发方法中,插件式编程是一个绕不开的话题,它涉及范围太广,很多编程人员在入职的初级阶段,一般都是维护一个小功能,或者维护一堆小功能,或者扩充一堆功能,但基本上不涉及到大的框架搭建,但作为提升之路,自己上了一个台阶之后,能够负责一个项目的整体搭建了,或者从头开始一个项目,就必须考虑构架了。插件式编程属于构架范围,我在这方面研究了几年,也实现了几个项目,想整理一下,当是自己的一个总结。
所以我准备趁小孩回老家了的这些天,写几篇关于这方面的文章,我经验不够多,只有12年的编程经验,且主要是用C/C++语言。在现在的公司,挂了个SE的职务,(我们都笑称为Sex Engineer,哈哈),经常会做些设计,但基本上没有断档写代码,我认为不会写代码的构架师,如果只画UML,基本等于在瞎说,当然,特别牛的构架师,不在此例。本文描述的插件式编程,使用C/C++语言做例子,后面也会给出一个详细的例子,供参考,也请高手们指导。我写代码的水平,应该高于我写文章的水平,文字多有不通之处,请海涵。
现在插件式编程目前的资料非常多了,但很多文章属于滥竽充数,希望我这篇能不被列入这个分类。所有一讲插件式的文章,都会举例以前的winamp,不知道还有人用这个播放器不,比较老了,经典的插件式编程。我这里就不说这个了,发散性,系统性的说这个话题。我这里给的例子全部基于windows平台,linux平台说啥好呢?还是不说了。看这个文章的人,应该90%都是工作于windows平台的。好了,不废话,正式进入这个话题。
2. 接口编程
要说插件式编程,先要说模块化编程,因为插件式编程是用来开发整个项目框架的,把一个项目按模块划分之后,可以并行作业,并减少关注的细节。而模块化编程,又涉及到接口编程模式。这些都是软件开发过程中,抽象与整理的一个过程。刚学习C/C++编程的同学,只会在main函数里面写自己的代码,不会把事情分成几个函数来做,等到会抽取函数,又无法做到公用性,代码像写日记似的,流水账一来就是几百行。这方面很重要的提升就是会提取公用函数,形成函数库。再到面向对象设计,写出自己的类,再到写出DLL。本文假设我说的以上这些,您都已经会了,会抽象公共函数,会自己封装设计自己的类,做成模块DLL,在主程序中加载DLL使用等。如果是C++语言,您一定要对virtual虚函数有完整的理解,对于C语言,则要完全理解回调函数。
接口编程,也不是什么新鲜事情,在C#和JAVA语言中,都提供了标准的接口关键字,C/C++没有这样的关键字,但接口编程是一种理念,不需要这些关键字,用普通的手法就能实现这个过程,我尽量避开讲语言层面的知识,因为这些知识,说多了容易说错,怕言多必失啊。
举一个例子吧,这是后面所有代码的“需求”,把这个需求实现的过程,就是一个接口实现的过程。我们要写一个网络爬虫模块,这个模块就2个函数:开始,结束。不需要其他的函数了,参数设置什么的,都忽略掉。现在要实现这个模块设计,给其他人留出接口,让他们写的程序,调用这个模块的“开始”函数,则实现自动爬网络数据的过程,调用“停止”函数,则停止爬数据,停下来即可。
1 C++接口定义
用C++语言,我们可以这样定义我们的接口:
class ICrawler
{
public:
virtual bool Start() = 0;
virtual bool Stop() = 0;
};
ICrawler* CreateCrawler();
void DestroyCrawler(ICrawler* pCrawler);
上面的virtual关键字,还有末尾的 =0,是C++的纯虚函数的定义范式,关于这个知识点,就不说了,请百度一下,如果虚函数的作用不清楚,您将看不懂以下的所有内容了,要不您先复习以下吧。这个类定义中,类名称也不是用“C”开头,而是用“I”开头,表示这个是接口的意思,当然,你换成别的字母也行,只是潜规则嘛,大家都这么定义接口,你懂的。定义好这样的接口之后,关健是实现这个接口。
C++的类需要有public这样的定义,我们知道C++的类实际上和struct是一样的,我们把定义修改成这样,这样也许更好看一些:
struct ICrawler
{
virtual bool Start() = 0;
virtual bool Stop() = 0;
};
ICrawler* CreateCrawler();
void DestroyCrawler(ICrawler* pCrawler);
这下好看多了,通过struct关键字之后,就把public去掉了。将以上的内容做成一个头文件,命名为“icrawler.h”吧。如果想要实现的更加的像C#或者Java,那么可以来这么一行:
typedef struct interface;
很多人都这么定义一下,不过我不喜欢,还是struct直观。
把以上的定义做成头文件,供大家调用,我们在模块内部,实现这个接口。先继承这个接口,然后实现它。代码大概看起来像这样:
class CCrawlerImpl : public ICrawler
{
public:
virtual bool Start()
{
//Start new thread.
_tprintf(_T("Starting ..."));
return true;
}
virtual bool Stop()
{
//Stop thread.
_tprintf(_T("Stoped."));
return true;
}
};
ICrawler* CreateCrawler()
{
return new CCrawlerImpl();
}
void DestroyCrawler(ICrawler* pCrawler)