`
同步更新于 WeChat:荣小菜在补钙 大家好,我是荣小菜,也可以叫我Richie,这次我打算分享一个LabVIEW OOP的实战,一个我自己摸索着写的仪表控制库。由于内容较多,要从底层慢慢讲起,预计要分几期进行。荣小菜能力有限,期间还望大家多多指正哈。以后的内容也会尽量同步更新于WeChat中,方便大家在移动端查看(不是广告哈^_^)。 1. 背景
测试领域常用的仪表包括:信号源、频谱仪、示波器、矢量网络分析等。自动测试的实现往往离不开对这些仪表的程控。当前仪表种类、型号很多,仪表厂家鱼龙混杂,虽然有了一些标准,但是想完成一个资源丰富的且兼顾可维护、可扩展性和易用性的通用仪表库还是有一定困难的。而我们异想天开,想做一个库,使用户只需调用一个函数即可实现对成千上万的不同种类不同型号的仪表的程控。
2. 信号源底层驱动 有想法就先动起来,首先就从信号源的最底层驱动开始设计吧,依照脑子里的第一反应,先采用简单工厂模式将信号源的底层功能给实现了再说。 UML类图如下,各型号(SMF100A、E8267D等)信号源继承抽象类(信号源),信号源工厂类负责选择创建信号源。
建立的信号源底层驱动库工程树如下:
3. 发现问题进行思考 此时我们只是匆忙建立了类和方法框架,还没有具体代码实现。但其实已经发现了不少问题,必须认认真真仔仔细细彻彻底底地考虑并评估这些问题才能保证我们不走弯路。问题如下: a) 信号源的方法如“打开RF”、“设置频率”等的数据结构是什么样的,20年之后呢? b) 我们要写的是通用仪表库,它将长期运行长期维护并包含信号源、示波器、频谱仪、矢量网络分析仪等种类仪表,每种仪表又有不同型号系列,这将是成千上万的仪表O_O。而我们是异想天开的希望用户只要调用一个顶层函数即可实现对这成千上万不同种类不同型号的仪表进行程控。 c) 这个库的实现和维护可能只有1个人。(这真是个悲伤的故事T_T) d) 根据开闭原则,有些方法如信号源工厂的方法 “Creator”一但写完,今后增加新的信号源时,方法中的代码绝不能被修改,因为这里是负责调用全部信号源类的中枢,否则当你写了100台信号源后却因为修改了这部分代码被迫去做回归测试,额,领导可能真的就要杀某个程序员祭天了T_T。 4. 解题思路 RE问题a):作为一介凡人,我们绝对无法预测今后20年各种信号源“打开RF”的所需数据结构(如果还存在信号源的话…),更别说其它仪表了。举个最简单的例子,以前的信号源,通道就只有一个,因此“打开RF”根本没有“通道选择”这一参数,而现在RS的信号源已经双通道了。。。设想一下当你写了100台信号源,难道要因为一台双通道信号源就要给他们都加入“通道选择”参数吗? 解决方法我感觉有两种: 一种是用策略方法,将“打开RF”提取出抽象父类,其他信号源的“打开RF”为子类具体类,即将可能变化的地方进行封装。思路应该是正确的,但是请想一下,当有100台信号源,每种信号源类似“打开RF”、“设置频率”等等这种功能设置有10个肯定有吧,那么一但写成这样,可能就要写1000个类和方法,再瞅一眼问题b)和c),光想想就要累死了吧。 另一种就可能比较“取巧”,我不确定这种方法是否“规范”,但目前还算“合适”。首先我承认“打开RF”数据结构变化的话只能修改全部信号源(因为方法的端口必须一致),但是我要避免一个个修改各信号源的“打开RF”的数据结构,因此肯定是要为“打开RF”的数据结构建立“自定义控件”,这样方便统一修改更新。其次,既然端口数据可能会发生变化,那我如果使用“变体”代替,就可以将变化“隐藏”,来防止一旦发生变化导致层层断线,使用变体后只需要关心底层“打开RF”里的变体转换即可。 这种采用变体的方法可能比较“异端”,个人感觉是各有利弊的,十分希望和大家讨论,请大牛指导指导^-^。
RE问题b):如果仅考虑信号源,其实再解决问题a)时不使用变体也是可以的,但是若是考虑到问题b),我们今后必须考虑上层架构对各类仪表的兼容性才行,而底层使用变体的优势也在此埋下伏笔,会在今后几期的讲解中体现。 RE问题c):要解决该问题,就更需要和大家一起云交流云讨论云编程了。 RE问题d):一般的工厂Creator方法会引入大量判断,在LabVIEW中就是大量条件结构分支,每次新增信号源都要修改Creator方法,增加新的分支,这违背了开闭原则。因此我是用了“反射”来解决,说白了就是主要利用“获取LV类名”和“获取LV类默认值(按名称)”这两个函数,根据输入的仪表型号自动创建出目标类。 这样只要保证类的命名规范,后续配合使用配置文件即可完成仪表创建。
注意,使用这种方法时,应在Creator私有数据中应包含个信号源类,否则生成程序后在Run-time环境下会报错。
5. 完善底层驱动 解决了上面的问题,我们可以继续完善代码了,将其它仪表的底层驱动也写出来。成型后的工程树如下。
底层驱动库完成后,我们如何调用呢,这就需要再次抽象设计,留待下期再讲吧。^_^ `
|