ISAE开发_来点设计模式#1

本文持续更新, 请关注

需求

集成式安全审计环境作为一个在设计上可以进行扩展的软件, 我打算写两种扩展系统. 一种是基于Qt的低级封装, 用来对安全审计环境进行大规模扩展, 比如集成一个浏览器, 一个代码编辑器等. 另一种是基于PythonLua的高级封装插件, 用户只需简单编写一些关于算法的代码即可装载运行, 例如Version 1.0中的数据流插件系统, 用户只需要写关键的数据处理函数即可, 插件的调用, 多线程管理, 界面绘制全部由插件系统来完成.

但是C++作为一个过于静态的语言, 明显不能按照Python的开发方式来. 首先是对低级封装插件的支持. 最开始是希望能将插件单独封装然后用时动态装载, Qt Creator给出了一个很好的例子. 但是究竟有无这个需求呢? 由于C++ABI换个平台就换个样子, 虽然可以用扩展C的方法保证ABI的稳定性, 但代码的编写和维护都会变得非常麻烦. 反正想做成VSCode或者JetBrains系那样完善的插件系统代价极高. 我想放弃动态扩展插件的另一个原因就是, 在这个软件的整体发展时间内我可能是唯一的编写者和维护者, 即使有合作者也是少数. 实现一个不稳定的低级插件系统代价有点高并且加重了软件不稳定运行的概率. 最终就打算直接写一个简单的插件管理系统, 不做动态装载, 将写出来的插件直接链接到插件系统里, 有多少插件就在插件系统里装载多少, 然后弄个指针map对插件进行统一管理.

高级封装的插件系统打算准备两套, 可以用C++轮子加速运行的就封装一下然后交给Lua直接调用. 然后提供PythonAPI接口, 基于系统Python交给用户自行扩展. 直接封装一个完整的Python解释器进去好像也可以. 但是暂时没有了解过这个做法. 等到数据流插件的Lua接口完善后就去研究一下Python解释器的完全嵌入.

在编写的过程中遇到了一些问题:

  • 所有的窗口控件都打算以插件的形式组织(即全部继承自一个窗口类), 便于绘制设置窗口. 但是窗口如何绘制? 设置如何存储与恢复? 信号应该怎么传输和链接?
  • 插件并不是分开组织的, 他们互相之间可以有依赖. 基于QMimeData的数据交流还好做, 但是像文件管理器中双击文件之后在编辑器中打开这种跨插件交流应当怎么完成? 把事件全部交给插件系统然后让插件系统来广播嘛?
  • 插件应当有多线程操作, 多线程的资源分配应当如何处理?

这些问题对我来说都是挑战.

插件管理与设置窗口的绘制

就像我咨询Apache553时他说的那样, 设置项本质上都是一些字符串, 然后经过不同的转换应用到程序上面. 所以我们可以在插件内部留一份默认设置, 和设置项的具体描述, 然后由设置窗口统一绘制. 有一说一这个实现起来不麻烦, 弄一个QTableWidget, 读取ini配置然后直接塞到设置窗口里即可. 但是这样用户体验肯定无比的差.

举个例子, 用户更改界面背景图片. 按照上面的想法, 用户需要填写图片的路径才能够正确更换. 如果能给一个文件选择按钮多好, 让用户打开文件管理器找半天路径, 然后复制过来, 用户体验差到了极点. 有了文件选择按钮也不舒服, 虽然填写起来比之前的反人类纯文本设置要好很多, 但是有些用户从网上直接下载的桌面背景命名一般长的比MD5都奇怪. 选择窗口也可能没有预览. 用户添加了自定义的桌面背景之后可能还想保留这个背景, 然后从已经添加过的背景之中随意切换, 这样的话能弄出来一个类似于带预览的背景选择界面是最好的. 这…全部交给插件系统来绘制的话, 绘制方式和绘制标准有亿点麻烦吧…

最终决定让插件的编写者提前把这个窗口编写好塞在插件里, 然后插件系统直接把窗口塞到总设置窗口里. 规格大概如下:

  • 设置窗口都继承自一个父类, 父类包含:
    • 指向插件主窗口的指针
    • QSettings* m_settings
    • 界面设计
    • applySettings()函数, 用来将设置应用到插件窗口
    • saveSettings()函数, 用来将设置保存到文件
    • loadSettings(const QString& path)函数, 用来加载path所指向的设置文件

然后设置系统在用户点击保存的时候只需要遍历所有的插件设置窗口然后依次保存即可.

插件管理与插件之间的通信

关于通信问题我打算弄两套方案.

首先是最传统的问题, 文件管理器插件中点击一个文件, 怎么把这个点击信号和文件的信息传给用来显示文件内容的插件?

苦思了很久也没想明白这个逻辑该怎么处理. 最后看完了设计模式恍然大悟.

这个可以用观察者模式来解决.

简单说一下设计思路:

插件都继承自一个父类, 这个父类应当有:

  • m_name: 插件的名字/唯一标识符
  • QList<QString> m_dependents本插件依赖的插件, 也是本插件作为观察者要监听的广播者列表.
  • QList<ISAEPluginWidget*> m_listener_list: 本插件是一个广播者, 拥有观察者列表
  • registerListener(ISAEPluginWidget* listener)注册函数, 向广播列表中添加一个观察者
  • removeListener(ISAEPluginWidget* listener)注销函数, 从广播列表中删除一个观察者
  • notify(QVector<QString> info)广播函数, 向所有的观察者传递信息. 其中info为传递的信息列表, 特殊数值信息应当序列化成字符串进行广播.
  • update(QVector<QString> info)本插件同时也是一个观察者, 本函数用来接收广播者发出的信息, 进行一些必要的操作(例如特殊数据的反序列化)之后交由其他成员函数, 对插件的当前状态进行更改.
  • QSettings* m_settings此设置项直接同步设置窗口类的设置项.
  • applySettings()将设置应用到插件.

多线程设计

UI界面就按照Qt默认操作, 不碰多线程管理.

麻烦一点的是应用设置. 有些设置可能需要比较长的处理时间. 这一部分交给插件自行解决.

评论

:D 一言句子获取中...