插件式开发体会:
自开始写【大话QT】系列就开始接触渲染客户端的开发,说是开发不如更多的说是维护以及重构,在接手这块的东西之前自己还有点犹豫,因为之前我一直认为客户端嘛,没什么技术含量,总是想做比较有挑战性的,为了这周总还专门找我谈了谈,算是“安抚”民心吧。正式谈话过后,我才决定接手渲染客户端的开发。
渲染客户端的所有构成均是采用开源框架拼凑起来的整体,细分它的组成大致包含以下开源模块,简单描述:
1> CTKPlugin插件系统框架。负责整个项目的架构,决定了项目采用插件形式开发维护。
2> Google protocol buffer。负责定义项目的通信协议,它是google内部使用的协议架构,最大的优点是:实现高效,向下兼容的通信协议。
3> Zeromq框架:负责项目中的网络通信,用于高性能网络编程。
4> 日志系统。负责项目中所有日志的输出。
其中,最为关键的就是CTKPlugin插件系统,它决定了项目的整体架构——采用插件式开发。经过这么多天的维护开发也深深的感受到这种插件式开发的方式带来的好处。以前,总是从课本上读到所谓的理想的“热插拔”式的插件开发,而我总是不以为然,我的意识里一个项目的开发多多少少都是臃肿的,在使用了这种插件式的开发方式后,突然感觉软件的开发、维护、升级变得很容易,下面说一下我体会到的几点好处:
1. 开发工作由之前的人等人变为并行开发。项目中插件系统分为两大部分:基础插件与应用插件,基础插件即通用插件,在其它插件系统中都要使用到的,比如:日志插件在每个其它插件中都会被使用;而应用插件之间则是相互独立的,比如:登录插件、文件管理插件等。基础插件一般是一些开源库,只需要我们编译出来使用即可,基本不需要我们自行开发;而应用插件功能的独立性决定了它们之间不会相互调用(业务整合插件除外),这样多个人员就可以独立开发,每个人负责一个独立的插件,项目进度会大大加快、周期缩短。
2. 测试案例容易编写,插件功能很方便得到验证。在一个插件的初始版本完成后,可以很方便的编写测试用例,来验证插件提供的功能性。由于插件系统最终提供的是动态链接库dll(windows下),而测试用例则可以建立为应用工程或界面工程,提供程序入口,加载调用插件中提供的方法。而且,测试用例可以保存在项目中(不会最终发布,最终发布的是插件的dll),如果将来插件使用出现问题,或者需要添加其它功能,或者升级均可以利用测试案例重新快速测试验证。
3. 系统业务逻辑变得异常清晰。如果项目不采用插件方式开发,每个功能均会杂糅在一起,无论是开发人员或者将来加入到项目开发的人都无法很快的了解业务流程,在分析这个功能的时候又涉及到那个功能。而采用插件式开发方式则每个业务逻辑很清晰明了,如果将来要调试3dmax的渲染模块,那么只需要阅读3dmax渲染插件就可以了,而且结合测试案例,很容易就可以上手。
上面就是这段时间以来针对项目采用插件系统开发的几点体会。
CTKPlugin插件系统介绍:
在CTKPlugin插件系统中要清晰地理解一个概念:插件是以服务的方式提供功能。每一个插件都有它的生命周期,在插件初始化的时候它会将自己的唯一实例注册到插件系统中作为服务提供,即上图中的register阶段。而当另一个插件需要使用到该插件提供的服务的时候就需要通过getService的方式获取。下面通过代码简单说明一下一个插件是如何想CTKPlugin系统中注册服务以及其它插件是如何使用该服务的:
1. 插件服务注册
每一个插件的实现都必须实现一个插件的声明周期类,它继承自CTKPlugin中的ctkPluginActivator,在ctkPluginActivator中定义了start与stop虚函数,插件的声明周期类必须要实现start与stop,实现服务的注册。
[cpp] view plaincopy
- class LHAuthPlugin : public QObject, public ctkPluginActivator
- {
- Q_OBJECT
- Q_INTERFACES(ctkPluginActivator)
- public:
- void start(ctkPluginContext *Context);
- void stop(ctkPluginContext *Context);
- private:
- LHAuth *m_Auth;
- };
实现类:
[cpp] view plaincopy
- void LHAuthPlugin::start(ctkPluginContext *Context)
- {
- m_Auth = new LHAuth();
- Context->registerService(QStringList("LHAuthInterface"), m_Auth);
- }
- void LHAuthPlugin::stop(ctkPluginContext *Context)
- {
- Q_UNUSED(Context)
- if (m_Auth)
- {
- delete m_Auth;
- m_Auth = 0;
- }
- }
其中:registerService即向CTKPlugin插件系统中注册该插件的唯一实例,而stop则是插件声明周期的终止。
2. 使用其它插件提供的服务
在其它模块中如果想使用登录认证插件,则在其它模块的Init阶段完成登录认证模块的加载,并完成初始化的功能:
[cpp] view plaincopy
- //! 初始化登录模块
- ctkServiceReference refAuth= d->m_PluginContext->getServiceReference("LHAuthInterface");
- d->m_AuthInterface = (qobject_cast<LHAuthInterface *>(d->m_PluginContext->getService(refAuth)));
- if (!d->m_AuthInterface ||
- (d->m_AuthInterface->Init(d->m_Parameters) != LH_SUCCESS) ||
- (d->m_AuthInterface->CreateInstance(varInstance, d->m_Parameters) != LH_SUCCESS))
- {
- qDebug()<<QObject::tr("Module %1 is invalid").arg("com.lht.auth");
- return LH_FAILURE;
- }
- else
- d->m_nAuthInstance = varInstance.toInt();
getServiceReference()即在CTKPlugin插件系统中获取LHAuthInterface服务。在初始化完成之后,就可以利用m_AuthInterface->Login()来使用登录认证插件提供的功能了。
项目如何使用插件式开发:
如上图所示,只是我这个项目本身实现插件系统功能的一个基本架构,相信不同的人使用会探索出更加有效,更加方便的使用方式。
每个项目都会有它的入口,我们不妨称之为portal,在portal中实现的功能很简单,最主要的就是完成CTKPlugin系统的初始化工作,待ctkplugin初始化完成之后首先加载lht_controller插件,lht_controller插件是很重要的一个插件,它主要负责完成其它所有应用插件的加载工作,如上图所示,它加载了lht_login登录插件、lht_mayaMaya渲染插件、lht_log日志插件、lht_goldenfarm渲染客户端插件(业务逻辑插件),然后执行业务逻辑插件,即lht_goldenfarm,而在lht_goldenfarm中根据业务逻辑实现不同的功能,调用不同的插件。
看上面的架构,很清晰明了,对于系统的维护很方便、容易。
本章总结:
好了,以上就是我这段时间开发收获到的东西,很多东西都是我以前开发中不注意的,现在慢慢当成规则严格要求自己,争取让自己的开发更加规范。
只有不断总结才能不断进步,还是验证了周总的那句话:“还是太年轻啊!!”。