从去年开始,就接触LabVIEW面向对象的概念,特别是学习了 @李时珍 的LVOOP相关的系列文章(推荐推荐,等着出书就买!)。但也只限于被动输入多,主动上手干少,一直以来有种迷雾中绕圈的感觉,似懂非懂,但又抓不到懂在哪里,不懂在哪里,有时候都无法表述出具体问题是什么。
LabVIEW面向对象编程zhuanlan.zhihu.com
最近,为了在参加NI公司的Actor Framework培训时能稍微听懂一些,再次下决心学习LVOOP,起码要先掌握面向对象编程的基础知识不是ヾ(◍°∇°◍)ノ゙。
先是花了一天看NI的官方帮助和范例,看完以后就差两眼一翻嗝屁了,脑袋变成三个大,有种考试时读不懂题目的痛苦……
峰回路转,翻着 @李时珍 关于Actor Framework的系列文章时,找到了文章里推荐的视频油管主的账号,里面就有讲LVOOP基础知识的视频介绍!强烈推荐呀,是LVOOP小白的福音。


视频一共十集,前六集就是很好的LVOOP基础入门介绍,包括概念的引出,实用场景介绍,并搭配了一个例子(油管主也很贴心的提供了例子的GitHub地址,喂饭到口边的感觉),简单讲解了程序编写。
视频1:主要是通过实际工作引出面向对象编程的需求、概念
油管博主在视频1中举例了一个日常工作情况:在和客户完成一次友好的需求讨论后,完成了软件设计、开发、测试和部署,实现了一次友好的合作。在接下来的工作中,甲方爸爸在已经完成的项目基础上进一步提出了新的需求,比如要支持新的硬件,增加新的功能。比如手上有一个程序,能够实现对34401的操作,甲方爸爸用着很满意,现在想实现对NI数表板卡的操作。对着甲方爸爸当然是说“好的,没问题,能干!”

问题来啦:我是重写一个程序,还是在原来的代码上改?原来的代码上改其实也和重写没啥太大区别,可能就是能复用部分子VI,如果整体流程没变,可能就是在代码里很多地方变成条件结构代码。其实整个设计、开发、测试和部署的流程一步都没少,还会在已经测试、部署过得项目中引入风险。
以上就是面向对象编程的针对问题了:在不改变已经工作的情况下为软件添加功能,不在测试过的代码中引入风险。
通过面向对象编程代码,将组件声明为类,类封装了LabVIEW functionalities VI中的功能,以及执行该功能或任务所需的集群数据。
油管主又举了一个例子:agilent或NI DMM可与CSV类和TDMS类进行通信,面向对象编程实现这一点。

接下来油管主讲解了类的的第一支柱:封装
类是一种自定义数据类型和与该数据交互的方法,这是一个集合,方法和数据集合在一起。
如图中所示,有一个XML类(XML.lvclass),XML类中的方法以VI的形式实现,例子中有Load XML File,Create XML File,Create root,Create Tag等等。其中XML.ctl是一个控件,类包含的数据都封装在CTL控件中,数据形式是一个簇。在油管主举的例子中类的数据是一个DOM(文档对象模型)引用句柄。用户可以在该簇中添加类的所有数据,是私有的(可以看到图标有一个钥匙,也是很形象了)。私有意味着只有类中VI才能方位类它。其他类的对象或子类的对象需要操作该类的数据必须通过该类提供的共有方法。如果测试时发现代码有错误,根据私有数据获取和写入得限定,错误惟一位置是类本身。

面向对象中类的这种形式,非常适合创建整洁的API,很多人会将其等同于函数库来用。油管主还推荐了使用第三方软件将函数添加到面板中的方式:JKI VIs包管理器。在学习了解这个知识点可以结合NIPM,VIPM一起学习,估计不久的将来 @李时珍 会将这方面的知识点形成一个系统学习的文章,期待。
但说回来,面向对象编程功能不仅仅是创建一个API或应用程序编程接口。说回到上面的34401和NI数字板卡,两者的操作都有“初始化”一项,但其驱动VI肯定是不一样的,会用不同的方式来做。如果在实际工作中知道软件运行时才知道连接的是哪一个硬件设备,哪一个“初始化”驱动VI应该被调用。这时候油管主引出了LVOOP的第二大支柱:继承。
继承允许子类重用基类的数据和方法,假设有一个名为DMM的基类,子类分别为agilent的子类和NI的子类。基类可以有一个占位符初始化DMM的VI,其中子类覆盖了它。因此,子类可以选择重写或追加基类的功能。
油管主这时候提出了一个新的例子,也就是提供GitHub地址的例子。有三个类:Camera类,Simulation类和WebCam类。

基类(父类)包含四个方法,分别是:Initialize camera, start acquisition,Get Image和Clean Up Camera。这四个方法在它们的程序框图上完全是空的,它们没有功能,它们是动态调度方法。这意味着Simulation和webcam的子类可以使用这些方法并覆盖它们。Simulation类覆盖了Get image。Get image的功能是加载文件,从文件上传一个图像并显示在屏幕上,作为模拟图像加载显示的。而webcam类决定覆盖所有四种方法,因为实际上需要与硬件交互,与硬件交互所需的数据是IMAQdx引用句柄。
“小结:面向对象的方法将数据与数据的操作方法放在一起,作为一个相互依存、不可分离的整体——对象。对同类型对象抽象出其共性,形成类。类中的大多数数据只能用本类的方法进行处理。类通过一个简单的外部接口与外界发生关系,对象与对象之间通过消息进行通信。这样,程序模块之间的关系更为简单,程序模块的独立性、数据的安全性就有了良好的保障。————《LabVIEW 8.2程序设计从入门到精通 ,CH16》”
接下来的视频演示了在LabVIEW中面向对象编程的实现过程
第二个视频分为了上下两集,上集介绍了如何创建类,如何使用分派方法。
首先,右键点击“我的电脑”,选择“新建”,单击“类”,然后被要求输入名称,点击“确定”。创建好类后,将自动填充ctl文件。保存后将用.lvclass扩展名文件来保存新建的类。
接下来,右键点击刚刚创建的类,选择“新建”,然后选择“基于动态分配模板的VI”。因为希望子类覆盖这个功能,所以是用该模板创建的。创建后点击保存,设置方法的名称。根据需求重复创建方法的步骤。
下集介绍了如何更改类的图标来创建好看的API,以及如何改变线的样式。
图标
右键单击类的lvclass文件,选择“属性”,这会弹出类属性窗口。

在类的属性面板中,点击“常规设置”下的“编辑图标”。在这里根据需求进行一些操作,如添加一些文本,这个会一直延伸到类中的所有VIs。


连线
另外可以选择类属性中“连线外观”,点击“使用自定义设计”。毕竟,类是一种自定义数据类型,有一条定制的连线也是很合理的。

完成以上图标和连线的设定后,点击“确定”,有一个消息出现,选择“是”。打开其中一个方法看看,如果我们打开一个捕捉图像,可以看到横幅已经被传播到这些方法,所以图标覆盖已经被应用。另外每个方法(VI)的图标可以双击,到“图标编辑器”中进一步定制图标,定制成更有意义的图标。
另外,类的控件和指示器也可以更改。打开控件文件,双击图标,可以像任何其他VI一样改变图标。通过定制,创建了一个漂亮的API,VI图标和控件图标。
在这一集中,油管主还介绍了 一个快捷键来打开 QuickDrop。在菜单控制选项中,QuickDrop默认的快捷键(英文输入法下)使用的是Ctrl+Space,Ctrl+Space会与微软中文输入法冲突,常常将快捷键修改为Ctrl+Shift+Space来打开QuickDrop。

QuickDrop打开以后,按快捷键“Ctrl+Shift+W”,完成自动连线+布局+清理。另外经常用的Ctrl+D,Ctrl+R。有兴趣可以使用一下,说实话刚用没感觉有多快,还因为输入法的问题弄了半天o(╥﹏╥)o。


第三集视频介绍了LabVIEW中的继承,子类如何重写基类行为。
继承给了面向对象编程巨大的能力,如何让一个类继承另一个类,如何让这些VIs实现覆盖,实现一些功能。在这个视频中油管主做了详细的解说。(我自己的例子是数表类,34401子类和NI DMM子类,父类中有三个方法,子类34401继承三个办法,NI DMM只继承初始化一个方法,纯粹是不想下载图像工具包)

右键单击类的lvclass文件(34401.lvclass),选择“属性”,这会弹出类属性窗口。转到“继承”。


点击“更改继承”,目前是从labview对象继承,更改,选择要继承的类,点击以后点击“继承所选类”就改好了。

改完以后,可以查看类的继承结构,我自己的例子是:LabVIEW对象在最顶层。然后是数表类,然后是34401类。

再右键单击类的lvclass文件(34401.lvclass),“新建”,“用于重写的VI……”(英文版为:VI for Override……Override即取而代之之意,即如该方法的输入是子类的对象时,该方法将调用子类该VI而代替父类的同名动态VI),父类的所有动态分派方法都出现在,选择好需要的办法后并点击OK。通过这样做,LabVIEW会创建很多用于覆盖的VIs。

选择要重写的办法(打开需要修改的VI),实现一些功能(编写代码)。比如子类34401中输出VI的代码如下:

第四集介绍了如何实现类方法之间的数据传递?
有了功能后,希望能将引用传递给API中的下一个。这里将使用对象。对象是类的单个实例,具体表现是VI顶部的连接。把数据与对象绑定,就能通过API传递它。
对象控件和一般控件一样,可以使输入、输出、常亮。通过“按名称捆绑函数”、“按名称解捆绑”实现对该对象所包含的数据的设置或获取。
双击34401.ctl文件,并粘贴任何想要传递的数据类型,在我自己的例子中是一个VISA资源引用。

在34401子类中初始化VI中,将使用“按名称捆绑函数”,将数据传递到对象连线。

如果要在34401子类中关闭VI中使用这个数据,使用“按名称解捆绑”函数来获得引用。

第五集介绍了LabVIEW中动态调用VIs的强大功能,也就是演示了一下使用。
我自己的例子中,父类数表类的CTL文件数据是空的,初始化VI,输出VI和关闭VI的程序框图里都没有写任何功能代码。

子类34401的数据和方法VI都根据需求更改了(手头没有硬件,就用对话框模拟一下)



NI DMM的初始化VI为

编写一个test.VI,如下图,拉出数表类中的初始化VI、输出VI、关闭VI,完成连线。
接下来就是在具体使用类时实例化为具体对象。将lvclass拖动到VI的前面板或程序框图中,就可以得到类的一个实例。本次实践依次实例化了数表对象、34401对象和NI DMM对象。比如双击程序框图中的初始化vi图标,弹出的对话框会显示34401子类的初始化VI和NI DMM子类的初始化VI,这是因为对初始化VI进行了动态调用。每个对象封装了自己的数据,不会影响其他对象。动态方法调用哪一个VI是直到运行时由输入的对象类型决定的。

当数表对象连线,运行后,什么事情都没有发生,因为数表类是一个抽象类,没有功能,它的方法VI中没有任何功能实现

34401对象连线后,运行效果如下:



NI DMM对象连线后,运行效果如下,点击一个对象框图后就运行结束了。

其实从连线后的颜色也能看出来,运行的是哪个VI。
小结:
通过以上几个视频的概念介绍和实例讲解,已经可以看出面向对象编程带来的好处就是:
“可维护性:通过把系统设计成多个独立组件的集合,系统各部分之间的依赖关系变成了简单的数据交换。当系统对某个组件进行更改后,只需要对该组件进行测试而不需要对整个系统重新测试或者发布;
可扩展性:当需要增加新的功能时,只需要增加新的组件或者对原有的类进行继承,而不需要重写整个系统;
可重用性:由于每个对象封装了自己的数据,因此调用某一个对象的函数并不会影响到同一个类的其他对象,也不需要编写新的VI来支持多个对象。因为改变一个对象中的数据不会影响任何其他对象,因此用户能够在多个应用程序中重用同一个类而不用担心会产生任何冲突。
——《LabVIEW 8.2程序设计从入门到精通 ,CH16》
”
第六集介绍了如何动态选择要执行的类(待续)