购买网站在线客服系统/东莞做网站推广的公司

该系列一共是9篇文章,本文是该系列的第二篇,是讲述通用的面向对象编程设计原则(SOLID),LabVIEW作为一门具备面向对象编程范式的图形化编程语言当然也要遵循上述的通用设计原则,当然除了接口隔离原则无法直接套用外。
本系列的首篇文章提到了在学习LabVIEW的面向对象使用时,往往都会先去看看原厂的随机帮助系统和样例,但是这些只是提供基本概念(封装、继承、多态)及如何去做(How To)的操作细节步骤,并局限于对面向对象编程(LabVIEW Object-Oriened Program)的基本知识点介绍,根本无法找到某个问题较为直接的参考答案。
当解决问题的需求无法得到直接回馈时,最为便捷有效的路径就变成了利用搜索引擎到网络上看看,搜索“LabVIEW 面向对象”,返回的搜索结果中往往会有一个更加火爆、火热、火辣的词语不断冲击着你的眼球——“设计模式”。
初学者往往接触这些概念后,缺乏对合理抽象的认识,拿着“设计模式”的锤子,应用封装、继承、多态等基础面向对象技术去找实际开发问题域中的钉子,结果似是而非,倒果为因,本来用过程代码能够简单完成的项目,使用LVOOP技术后,反而变得繁琐复杂,难易掌控,到处堆砌的设计模式中仿制代码,开发进度难以维系,从而导致实际工程代码开发往往以失败而告终。
与之相对比的是,面向过程到反而能够屡屡完成基本基本目标,虽然面临着后期维护困难以及难易扩展的问题,但是起码能够交付使用。
其实,纵观LabVIEW面向对象编程技术栈体系中,还有两个重要的内容较少提及(或者难易讲解清楚的方面)面向对象分析(OOA)和面向对象设计(OOD),真正的面向对象应该是合理应用面向对象设计原则,抽象本质,高层概念复用,针对接口编程,实现高内聚低耦合的解决方案。
本篇和后继文章主要介绍面向对象设计原则(SOLID),以及应用这些原则形成的LVOOP中HAL(Hardware Abstraction Layer)和MAL(Measurement Abstraction Layer)的面向对象技术在实际工作中的解决方案。
提到面向对象设计领域中,前人圣贤们为我们留下了丰富的知识遗产,而一本圣经般的图书《敏捷软件开发:原则、模式与实践(C# 版本)》(以下简称为APPaP图书,另外该书有两个版本,最早出版的用C++和Java编程语言作为示例,后来又出版了C#语言的版本)通过归纳汇总,为我们提炼总结了五条经典设计原则,其英文首字母缩写为SOLID,正好寓意为面向对象设计的基石(Solid),在本系列博客文章中只是简要的罗列个人的理解以及原则LabVIEW测试测控领域应用的特点,详尽的论述说明还是特别建议大家直接阅读原书。

SRP:单一职责原则
一个类应该只有一个发生变化的原因。
这条原则实际上也是代码内聚性(Cohesion)的体现,即表征一个模块的组成元素之间的功能相关性。
SRP往往用来指导类该进行如何设计:举例说明,在我们开发自动计量测试程序领域中,当我们为直流电源硬件设备进行抽象表示为对象时,往往容易将其技术性能指标和硬件功能封装到一个对象中,单一实物对象直接映射到单一逻辑对象而形成一个大“胖”类,而实际上这是两种不同的职责,一个负责输出直流电源能力,一个负责详尽描述在不同条件(电网效应、负载效应)中输出的指标特性,按照APPaP图书中的解释:“因为每一个职责都是变化的一个轴线,当需求变化时,该变化会反应为类的职责的变化。如果一个类承担了多于一个的职责,那么引起它变化的原因就会有多个...这种多余的耦合将会导致脆弱的设计!”,诚如书中所言:SRP看似最为简单,但是确实最难应用的原则,往往初始设计的职责很难清晰的摘开,只有通过不断的测试、反复开发并重构,才能磨练出最佳的职责抽象分离。
OCP:开放-封闭原则
软件实体(类、模块、函数等)应该是可以扩展的,但是不可修改。
该原则是面向对象设计最终所要实现的核心效果之所在,依照该目标效果原则进行设计就可以为我们带来面向对象技术所声称的巨大好处:灵活性、可重用性以及可维护性。
开闭原则中的开放是指提供服务(功能)的模块可以通过继承和多态基础进行动态扩展,而使用方(客户程序)包括整体框架可以继续得到概念性的整体复用,从而达到不需要修改使用者的代码,特别是不必改动源代码或者二进制代码(备注:目前的LabVIEW版本还无法做到二进制代码兼容,为我们的面向对象代码可执行文件发布带来一个巨大的天坑,后面在第五部分内容插件框架问题发布中还有详细说明)。
提供扩展能力就是抽象的魅力,通过抽象基类封装派生类的行为差异变化,通过配置文件实现设计模式中工厂模式进而动态实现替换功能。在此过程中你也会综合应用到其他的原则如SRP、LSP、DIP。
实际在本次直流电源检定/校准工程代码开发过程中,被测设备电源(如6674A、6622A、E3642A等)具体类型的扩展、测量标准设备的可互换、以及要验证被测试件的指标的具体差异性,均需要完成以上扩展性的抽象,保持高层调用测试框架测试状态机结构的高层复用性。
LSP:里氏替换原则
子类型(subtype)必须能够替换掉它们的基类型(base type)
要想达到OCP的效果,需要继承和多态这两项面向对象编程技术做支撑。正是使用了继承与多态的组合应用才能使得我们能够得到可以灵活扩展的插件(Plug-in)结构,而要想保证这种结构运转良好,无需修改,就需要里氏替换原则来保证我们创建的类层次概念复用的健壮性。
实际上,里氏替换原则更加强调的是一种基于契约设计编程,即满足提供通过接口对外服务的行为方式模型代码与它的客户程序间约定。
因此,里氏替换原则强调了使用继承的核心要素就是契约式的接口概念继承,是为了达到OCP的效果,从而实现高层框架概念更好的复用,动态扩展类型的插件式的被调用,而不是为了复用代码而抽象成类层级继承结构。
OCP是面向对象技术宣扬美好效用的关键核心,而LSP是使得OCP成为可能的主要保障原则之一,正是子类型的可替换性才使得使用基类型表示的模块在无需修改的情况下就可以进行动态扩展,而这种可替换性必须是开发人员可以隐式依赖的,通过设计保障的,如果违反LSP,那么OCP中扩展特性和防止修改就无从谈起。
ISP:接口隔离原则
不应该强迫客户程序依赖并未使用的方法
这条原则更像是一条补救折中原则,就当一些对象(“胖”类)确实需要非内聚(即违反了上述设计原则的SRP单一职责原则)的接口的情况时,ISP建议客户程序不应该看到它们(“胖”类)作为单一的类存在。
相反,客户程序看到的应该是多个拥有内聚接口的抽象基类(或者接口类型)。从技术层面上讲,是通过增加一个内聚接口的抽象层,隔离了“胖”类与客户程序之间产生不正常的并且有害的耦合关系。
DIP:依赖倒置原则
a.高层模块不应该依赖于底层模块,二者都应该依赖于抽象。 b.抽象不应该依赖细节,细节因该依赖于抽象。
这条原则是指导我们如何做面向对象框架(Framework)设计,编制面向对象程序的最为核心的原则!为了应对复杂性,我们在开发程序的时候往往需要分层、分模块的技术手段将大问题分解,通过层次清晰的定义,完成上层模块对下层模块的调用。在面向过程的编程范型中,高层模块是依赖于底层模块的,抽象的逻辑概念是依赖于编程细节的,这也直接导致无法达到高层概念框架复用,以及动态扩展的维护弊病。让我们看看实际的编程代码效果

上图是我们以往旧有直流电源负载效应的测试工程代码框图的部分截图,直流电源负载效应是指直流电源在空载和额定载荷下(带载)下输出电压(或电流)的变化性。
从程序框图中我们可以看出基本的测试流程,“先是用数字多用表测量空载输出电压,控制直流负载设备加载,等待稳定时间后进行带载下输出电压测试。”
上述的测试策略实际上就是直流电源负载效应测试的核心抽象,也就是我们的高层模块概念,但是在过程式编程范型中将这些抽象直接映射为细节具体化了,数字多用表的角色概念直接映射为Ag34401A,被测直流电源的角色概念直接映射为Ag6674A(HP66xxA的驱动),而直流负载的角色直接映射为Ag606x,技术指标直接分解为数组结构和簇结构,直接映射导致了高层算法测量模块即直流电源的负载效应依赖于具体型号实施细节。当依赖于细节以后,将会带来扩展困难、维护困境,例如下图表所示:

在以往的我们过程式编程范型中,我们通过高层模块动态调用函数来封装技术指标和被测设备的驱动差异性,使用Case条件结构来封装标准设备的可替换性,由于都是依赖具体的实现,因此导致扩展性需要进行源代码修改,并且由于没能通过抽象层进行概念角色的封装依赖,导致代码有错误的时候,维护修改需要打开所有的项目.Vi进行一一修改,特别是电源的型号种类繁杂,并且还有量程与通道的之间的组合关系,修改量及其庞大无比,维护此类代码简直能够让人崩溃到怀疑人生的意义何在!?痛苦不堪!
回到我们要研习的核心设计原则—DIP: 依赖倒置原则,从上述旧有代码的层次结构使得高层模块(策略)之间依赖于具体的底层实现模块,这也就意味着底层模块的改动就会直接影响到高层模块,从而迫使它们依次做出改动。而实际上我们希望能够得到高层模块的概念复用。
在日常工作中,当作为计量工程师的我们学会某类设备的计量校准时,是具备举一反三扩展到其他同类设备的工作类型中,而编程中却重复再重复的拷贝同样的代码段在不同的项目Vi中,在以往的编程过程中,我们被局限到了只是提供子程序库来重用底层模块(提醒,也就是前面文章中提到的当你查看帮助系统配置文件如何使用时,可以直接借用代码样例复用的的模式),缺乏对角色和概念的高层次抽象,并且由于高层模块依赖于底层模块,那么在不同的上下文中重用高层模块会变得异常困难,而如果高层模块能够独立于底层模块,那么高层模块就可以非常容易被重用,其实这也是面向对象框架如何进行设计的核心原则。
应用该原则的核心其实就是抽象,要依赖于抽象,进行本质的概念抽象和行为角色抽象,以及进一步的交互接口数据封装的抽象。
该原则具体细化可以得到下列内容:
- 高层模块依赖于抽象层(高层模块基于使用抽象层编程);
- 底层模块依赖于抽象层(底层模块继承抽象层);
- 细节依赖于抽象(底层模块提供实现细节来实现继承扩展抽象,内部算法依赖于抽象接口进行通信)。
就如经典图书《敏捷软件开发:原则、模式与实践(C#版)》(APPaP)中所总结到:“传统的过程化设计所创建出的依赖关系结构,策略是依赖细节的,而面向对象设计倒置了依赖关系结构,使得细节和策略都依赖于抽象,并且往往是客户代码及上层机构拥有服务接口,下层提供继承与扩展!”。
DIP依赖倒置原则是正确创建可重用的框架来说是必需品,由于抽象和细节彼此隔离,所以代码也就非常容易维护与扩展。

总结SOLID面向对象设计原则:最为核心的就是DIP依赖倒置原则,用于指明我们如何依赖抽象进行设计,SRP单一职责原则用于设计出符合内聚的单个类,进行扩展使用继承加多态组合时我们要保证符合LSP里氏替换原则,不能破坏整体继承体系对外的服务接口契约。ISP则用来指导接口设计以及弥补胖类的SRP意外情况。最终达到面向对象程序开发的效果原则OCP开闭原则,发布程序后,无需修改,插件化动态扩展,实现编程代码的规模经济化。
参考文件
《敏捷软件开发——原则、模式、与实践(C#版)》,Robert C.Martin , Micah Marin ,邓辉,孙鸣, 人民邮电出版社,2008年。
《面向对象葵花宝典——思想、技巧与实践》,李运华,电子工业出版社,2015年。