78.模块接口 API 的两种设计方案
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
模块接口API 的两种设计方案
假如你要设计一个程序模块,它的功能是读写INI 文件。用户调用这个模块,就可以方便的把信息写入INI 文件,或从其中读出信息。
你将如何设计这个模块的接口呢?LabVIEW 中常见的方式有两种,第一,为模块的每个方法都做一个子VI,比如写数值型数据的方法做一个VI,写字符串的做一个VI,读字符串的一个VI 等等;另一种方案:把所有的方法都放到一个子VI 里去,用户通过一个变量来选择运行哪个方法。
这两种方案各有优缺点。第一种方案符合一般人的思维模式,更容易让用户理解和学会使用。现在LabVIEW 中处理INI 文件的模块采用的就是这种方案。每个用户可能用到的方法(甚至是每一种数据类型),都有一个对应的VI。维护起来也容易,哪个方法有bug,到它对应的那个VI 中去调试就可以了。
但是打开这些处理INI 文件的VI,他们调用了一个更底层的模块,这个模块采用的是第二种接口方案。所有对INI 文件底层的操作,都被放到了一个子VI(Config Data Registry.vi)里。用输入参数("function")来控制执行不同的功能。
这种方案也有它的好处,我看过一本叫做《软件工程方法在LabVIEW中的应用》的书,它的内容用一句话来概括,就是号召大家把模块都写成上述的第二种方案。不过我们先来说一下着第二种方案的弊端。
首先,给外部用户的感觉就不如第一种方案那么清晰易学。如果把所有方法分开成独立的VI,用户可以只专注学习自己可能会用到的功能对应的VI;而第二种方案,所有功能在一个接口VI 里,那就强迫用户把所有功能都要了解一下。
其次,每种不同功能所用到的参数都不尽相同。采用第二种方案,就意味着这个唯一的接口VI 要包含所有方法时用到的控件(参数)。所以这个VI 上的控件会比较多。并且,有的控件在调用不同功能时,用途(或者说所表达的意思)不同。这样不但会造成用户学习的困难,在使用时,也非常容易出错。
还有一条,第二种方案的效率在某些情况下非常低下。我们把一个模块提供给用户,但用户不见得会使用这个模块中所有的功能。第一种方案,用户程序是在编译时选择使用模块中的那些方法;而第二种方案是在运行时选择使用什么方法。如果用户只用到一个模块中的一两个功能,采用第二种方案,只用用户用到的方法相关的代码才会被链接到它的程序中;而采用第二个方案,不论用户是否需要,整个模块都会被链接到它的程序中去。
这是因为这几个缺点,造成现在LabVIEW 提供给用户的库中,几乎都是采用的第一种接口方案。
但是,着第二种方案,一度是LabVIEW 程序设计中一个非常流行的方法,自然也有他的优点。
其一是更好的解决模块封装的问题。在LabVIEW 8 之前,LabVIEW 本身不支持面向对象编程,也没有提供对一个模块进行封装的功能。我如果编写一个功能模块给用户,我这个模块中所有的VI,即便是我只把它当作内部使用,都可以被用户调用。这是很不安全的,因为内部VI 随时都可能被改变调整,从而引起客户应用程序的错误。如果所有的功能都通过一个VI 暴露给用户,则用户更容易搞清楚只有这个VI 他可以用,其它的VI 都是不能被他直接使用的。并且这样也可以使自己编写的一大堆VI 看上去也更像是一个模块或组件。
LabVIEW 的另一个问题是,它作为数据流驱动的编程语言,不像文本语言那样可以方便的使用全局或局部变量。在LabVIEW 中使用全局或局部变量不但效率查,还会严重影响程序的可维护性。我编写的模块,它所用到的内部数据如何组织呢?全局变量既然不好,那就只能考虑使用移位寄存器了。
LabVIEW 程序如果设计的不好,数据在不同节点间传递时会产生很多份拷贝,造成效率低下。为了解决这个问题,最好是我内部使用数据,就不要再在VI 之间传来传去了。打开Config Data Registry.vi,你会发现这个VI 的主体框架是一个只运行一次的循环。凡是这种只运行一次的循环,程序真正想利用的都是循环上的移位寄存器。这个VI 里的多个移位寄存器都是既无输入又无输出的,它们的功能是用来保存模块的私有数据。
用移位寄存器保存模块的全部私有数据,模块的所有方法都在移位寄存器之间完成。这样数据始终在一个VI 内,避免了数据在不同VI 之间传递可能会引起的复制。这是很长一段时间内都相当流行的LabVIEW 程序模块设计思路,不过我觉得也许现在可以放弃这个方案了。
首先,这个实现方法只适合功能简单的小模块,模块的大部分代码都放到一个VI 中。如果模块数据功能较多,还用这个方法编出来的VI 就很难读懂,没法维护了。Config Data Registry.vi 虽然功能并不复杂,但代码已经不那么清晰易懂了。
如果这个模块在程序中只有一个实例还好办,若要支持多个实例,那数据部分就要设计个更为复杂以确保模块不同实例之间的数据不会混乱。
最重要的是现在LabVIEW 自身已经开始支持面向对象的功能了。在LVClass 中,既可以有数据,也可以有方法;方法可以被定义为是私有的或共有的;另外之支持继承、多态等。所有这些都为功能模块的封装和接口提供了更好的解决方案。与其费尽心机的自己想办法把格模块包装的更合理,不如直接利用LVOOP 已有的功能。把自己的的模块都设计为LVClass。