【大型软件开发】浅谈大型Qt软件开发(二)面向未来开发——来自未来的技术:COM组件。我如何做到让我们的教学模块像插件一样即插即用,以及为什么这么做。

时间:2023-01-13 22:08:43

最近我们项目部的核心产品正在进行重构,然后又是年底了,除了开发工作之外项目并不紧急,加上加班时间混不够了....所以就忙里偷闲把整个项目的开发思路聊一下,以供参考。

鉴于接下来的一年我要进行这个主框架的开发,本着精益求精的态度,加上之前维护前辈的产品代码确实给我这个刚毕业的社畜带来了不小的震撼,我决定在这个模块的开发中优化之前的开发模式,提升整个产品的健壮性和独立性。

开发一个大型软件最重要的问题有三个,一是如何保证每个模块开发的独立性 二是如何保证数据结构的一致性 三是如何保证程序的可维护性和健壮性。这几个文章的内容我会在几篇文章中分开聊聊我的做法,做个记录。

本篇文章聊聊如何保证各个模块开发的独立性——怎么让功能模块、教学模块的开发独立于主框架本身。让不同的模块之间尽量通过接口的形式进行交互,而抛弃传统的中转消息码->调用模块的模式,让实际功能以接口形式暴露。

这一期简单聊聊开发准备,下一期浅谈怎么在开发中我们项目如何保证模块开发的独立性。

技术背景

请在阅读以下两篇文章之后观看本文以获得更佳的体验,以免你不知道我在说什么。

Qt开发Active控件:如何使用ActiveQt Server开发大型软件的主框架

Qt开发:Windows 下进程间通信的可行桥梁:窗体消息

【大型软件开发】浅谈大型Qt软件开发(一)开发前的准备——在着手开发之前,我们要做些什么?

为什么?

我们谈到为什么要这样开发的之前,要先聊聊之前我们的框架。

我们的软件之前没有框架,就是一个大的C#程序,然后里面想到什么就塞什么,然后教学模块、主框架、功能模块之间就通过一个C#事件委托向上传递。教学模块中没有办法直接引用所有的功能模块,而是通过一个函数里面传参,再转移到主程序中去。

【大型软件开发】浅谈大型Qt软件开发(二)面向未来开发——来自未来的技术:COM组件。我如何做到让我们的教学模块像插件一样即插即用,以及为什么这么做。

这个模式本身是没有任何问题的,但是也可能是开发人员当时在开发的时候没有考虑过这个项目要维护十多年,导致整个程序在后面变成了一个几乎不可维护的庞然大物。

这里功能模块是几乎嵌死在主框架内的,这个是可以的,因为功能模块一般是调用外部的DLL或者对外部exe的管理,一般会和这些模块之间有深度的交互。而且一般我们也很少更新新的功能模块,光是现有的功能模块就几乎够了,之后的更新哪怕是直接嵌入在主框架内也是没有关系的。

当然了,功能模块也是一个个DLL,但是原先的调用是在使用的时候去尝试实例化一个功能类,然后再调用其中的方法。这样就涉及到调用、事件绑定等问题。

最严重的问题还是来自教学模块的信息向上传递。这个是由于引用导致的:

1.我们不可能让每个教学模块去引用各个功能模块,这样主框架完全脱离了教学模块的掌控,那相当于是每个教学模块重新开发了。而且每个功能模块并不能直接这样使用,需要的很多参数和消息都必须在主框架中获取或者初始化。比如座位信息,我们这个产品有一套很复杂的座位信息数据。还有一些比如MAC地址等。如果独立获取会极大的提升整个产品开发的维护难度和开发难度。

2.只能由框架去调用教学模块,这也就导致消息没法直接向上传递,只能通过类似回调函数的事件委托将事件以类似 key-value键值对的形式向上发送。这样我的主框架就要写一大堆if else去根据每个key去判断教学模块发这个消息想表达什么了。

我这里展示其中一部分就知道有多逆天了:
【大型软件开发】浅谈大型Qt软件开发(二)面向未来开发——来自未来的技术:COM组件。我如何做到让我们的教学模块像插件一样即插即用,以及为什么这么做。
【大型软件开发】浅谈大型Qt软件开发(二)面向未来开发——来自未来的技术:COM组件。我如何做到让我们的教学模块像插件一样即插即用,以及为什么这么做。

当然就像我说的,这是由于早期设计的时候完全没有考虑程序的拓展性开发所用的妥协性开发策略。在一开始确实方便而且好用。在后面这样一大坨if else让整个代码维护起来简直异常困难,更别提在接到消息之后这里还需要小小处理一下。

3.教学模块没法独立开发,必须依托于教师端。怎么说呢,就是教学模块几乎完全没法脱离教师端进行开发,因为要教学模块也需要暴露一大堆方法和接口供主框架去交互。模块设计操作起来就非常恶心了。

甚至!我们之前有过用exe的应用程序,所有的信息交互都是通过SendMessage进行的!你知道那对我幼小的心灵造成了多么大的伤害吗!这样的教学模块几乎是完完全全嵌在主框架内的专用exe,几乎不存在什么扩展性和维护性,因为除了问题除了开发者本人,别人根本没法改,也不知道怎么改。

或者我们换一种说法:教学模块这样设计就只能让主框架开发人员开发,且很难多个模块之间平行开发。

怎么做?

这就不得不提到面向未来的技术:COM组件

COM组件可以让主框架提供接口,然后各个教学模块就可以直接去调用主框架提供的接口。这样就直接替换掉了我上面说的if else判定这种僵硬的开发方式。

这个是ActiveQt Server向外公布的接口文档
【大型软件开发】浅谈大型Qt软件开发(二)面向未来开发——来自未来的技术:COM组件。我如何做到让我们的教学模块像插件一样即插即用,以及为什么这么做。

这里提供的Public slots 就是主框架提供的方法。这样我们就可以通过直接调用接口的方式调用主框架内部的服务了。

也就是说框架实际上和之前的那个if else也差不多,区别就是主框架不需要在接收到消息之后进行检索,可以直接调用接口了。也就是说不需要维护那么长一条if else 链,而是暴露接口,管理接口即可~

就这样?当然不止,在这里我还提供了一个新的教学模块开发的范式。原来的教学模块 就像静态调用的DLL,而现在我们希望这个教学模块能像插槽一样即插即用。

该怎么做?既然是即插即用,那么就得请出我们的动态调用DLL 方法,接下来我将给出我的框架设计。

框架设计?

框架设计分为教学模块设计和主框架接口设计,这两部分实际上都是对主框架的设计,不过涉及的内容不一样。

教学模块框架

教学模块框架的涉及我需要给出一张图来做表示:

【大型软件开发】浅谈大型Qt软件开发(二)面向未来开发——来自未来的技术:COM组件。我如何做到让我们的教学模块像插件一样即插即用,以及为什么这么做。

这个部分看图吧,我懒得解释太多了,这个图我觉得写的相当清楚了。

流程如下:

1.通过读取配置文件确定有哪些模块是可以尝试去调用的

2.在本地文件夹中检索可以调用的模块是否存在

3.调用教学模块约定好的方法来确定当前模块是否可用。

4.读取本地锁控,确定指定模块是否有锁控权限。

注1:因为是动态加载的DLL,那么DLL就必须提供类C接口,也就是extern "c" __del什么的,我忘了,反正就这么回事,自己百度查一下吧。

主框架的接口设计:

【大型软件开发】浅谈大型Qt软件开发(二)面向未来开发——来自未来的技术:COM组件。我如何做到让我们的教学模块像插件一样即插即用,以及为什么这么做。

如图所示,我们在Interface_Kernel中提供了整个主程序中挂载的所有功能模块的单例调用接口,并在Interface_Kernel的单例中进行管理,然后再通过单例的方式直接向功能模块申请功能。这样既避免了大量重复啰嗦的消息码发来发去,也可以减少很多重复开发的工作量。

这样操作,主框架就不需要提供一个单独的类来管理教学模块和与教学模块进行交互了,而是可以只需要关心教学模块的启动、关闭、显示、隐藏 即可,其他的功能都由Interface_kernel单独交互即可。

这里我写了一个完整的工程,但由于代码是公司的财产,在此我不能开源所有的代码,如果你是将来维护开发的人员,你应该可以根据这篇博客了解到为什么主框架代码中会有接口类还有一个Interface_Kernel,原因也很简单,因为每次外部绑定主程序的时候,都会实例化一个新的接口,需要一个核心来管理所有的接口类实例。

至于说教学模块DLL的管理,也是类似的,但是这里就不展开细聊了。

总结

COM组件为功能模块的集中管理提供了可能,同时也为教学模块的独立开发提供了可能。

整个新框架和旧框架的核心思想区别就是:就框架把所有的教学模块都当成了主程序的一部分。

而新的框架中主程序其实只是为所有的教学模块提供各种各样的服务,比如语音、视频等服务,教学模块只需要调用指定的COM接口就可以直接调到主程序中的工具,而不需要再进行模块间的交互。而教学模块本身也只需要提供开启、关闭、显示、隐藏、认证五个接口,不需要再像之前一样写一大坨东西管理教学模块了。

这样既为后续模块化开发提供了更多的便利,也让主程序更加简洁,减少了很多不必要的重复开发。

如果有什么讲的不够详细的地方欢迎站内私信,我会尽量解答。