在OSEFA中,我们不需要编程,只对黑箱组件进行选择、参数化和配置,就可以构造出制造单元的软件控制器。
看看这句话,是否有似曾相似的感觉?同样的话术,我们在低代码/无代码的各种业务场景中,已经反反复复看到或者听到,关键的内容就是“我们不需要编程”。没想到,这句豪言在23年前的一本书上看到了,这本书就是《特定领域应用框架:行业的框架经验》。
本书的英文版Domain-Specific Application Frameworks: Frameworks Experience by Industry是系列书的第三本,由John Wiley出版社于1999年10月18日出版。以计算机技术发展的更新频率来看,完全可以称得上是祖母级的IT著作了。
前面引用的内容是对OSEFA框架的设计说明,该书第四章则介绍了OSEFA框架的历史。它的Windows版本与OS/2版本由德国康斯坦茨高等专科学校开发,使用了C++语言在1993年到1996年期间完成。如此算来,该框架距今已有26年了。换言之,在26年前,已经实现了一款无需编程的低代码框架。
如果要对低代码做进一步考古,或许还能继续追溯到更早的时代。这里还是结合书中的内容谈谈OSEFA框架的设计,了解它是如何做到无须编程即可为加工制造单元创建应用的。
01冻结点和热点
OSEFA框架通过冻结点(frozen spot)表示共性的业务,通过热点(hot spot)表示可变性。这是框架的设计基础。
由于它专注于工业制造和控制领域,通过对业务的梳理,很容易识别出冻结点和热点。
OSEFA定义了两种类型的冻结点:
- 结构化的冻结点
- 加工逻辑相关的冻结点
结构化的冻结点实际上就是针对业务进行领域建模,以仓库为例,就是识别出组成工作站拓扑结构需要的元素,如工作站、单元仓库、机床等。有些元素是原子的,有些元素则由原子元素组合起来。由于组成结构体的逻辑是可变的,冻结点仅仅包括这些元素,而将组合元素的逻辑分离到配置中。这些配置,在如今的低代码实现思想中,可以理解为是元数据。
典型的加工逻辑冻结点是单元控制软件并发地执行所有工作站上的加工序列。加工序列本质上是可变的,但组成加工序列的加工控制对象,以及对象要执行的命令却是不变的。同时,还可以在更高的抽象层提炼出通用的加工序列步骤,这些步骤也是不变的。
要有效地应对变化,就需要控制不变的部分,然后将变化的内容从不变的部分分离出去。因此,定义出冻结点的位置,往往也是发现热点的地方。结构化的冻结点,自然就有结构的可变性需求,如机器的数量、种类及类型。这些信息就是加工控制对象的具体值。在概念上,冻结点相当于领域概念类型,而热点这是领域概念实例的值。在实例化这些对象时,可通过读取配置在文件或其他存储介质的值,动态满足结构变化的需求。
另一种分离变与不变的方式是将通用的概念进行抽象,然后将特定的实现放到更低层次的子类中。这是面向对象设计中最常见的原则。不同的抽象层次,体现了不同的共性。正如书中所说:“因为热点引入了表示新的通用概念的类,并由此在类结构中引入了新的层,所以框架包含的层比专用应用中的层更多。”
表示通用概念的类虽然通过识别热点获得,但它本身应是稳定不变的,属于冻结点的内容。《设计模式解析》一书中提到的共性与可变性分析表达的正是这一思想。
框架分离了冻结点和热点,并为热点定义了专门的热点子系统。“一个热点子系统包括一个抽象的基类、若干具体的派生类,还可能包括附加的类和关系。”
在框架之中,热点子系统与它之外的功能是如何组合起来的呢?因为OSEFA框架采用C++实现,故而采用了动态绑定的方式。书中介绍道:“框架的对象使用具有热点子系统的基类类型的多态引用向热点子系统请求服务。”对于支持元数据的语言来讲,则可采用反射机制,结合依赖注入来实现动态绑定。
02领域模型
OSEFA支持低代码能力的另一个关键是定义了包括传送设备、加工设备、物品抓取设备、仓库等领域对象,它们提供了与机器或设备无关的领域职责,构成了框架的领域模型。
领域对象需要和标准机器设备层的机器或设备进行协作。机器和设备是对具体硬件的一种封装,定义了各自的接口,以接收对应的服务请求。标准机器设备层需要接收的服务请求和领域对象的服务请求并不相同,故而领域对象需要完成两种服务请求的映射,并将映射后的服务请求传递给标准机器设备层的对象。
书中在介绍这部分内容时,有些语焉不详。我的理解,由于OSEFA针对的是专门领域,因而框架事先定义好了与之相关的领域模型,为加工控制层的策略类提供领域知识,而非采用领域模型设计器的方式动态进行领域建模。
03配置服务和配置器
低代码平台的主要原理是将变化的热点分离出去,然后尽可能转为配置方式支持可变逻辑的实现。实现逻辑可以通过代码生成,也可以利用通用执行引擎执行。通俗地讲,就是将原来需要硬编码的内容,改为配置方式来完成。OSEFA也不例外,但是为了简化整个配置过程,它还提供了配置服务。
这些配置服务有点类似模板,通过事先配置好一些常用的配置内容,就能减少配置工作。例如,许多加工场景都需要将机床与加工工作站关联起来。关联配置可能需要设置很多值与配置项,但考虑到这一关联操作是常用的,也是固化了的内容,就可以提供AddMachineToAMachineCluster
配置服务来完成。
框架的开发者还为用户开发了一个交互式配置器,使得用户可以无需编程就可以使用OSEFA来创建应用系统。使用交互式配置器可以显著减少创建应用所需要的时间和工作量,也避免了编写配置代码出现错误的情况,降低了使用门槛。
04运用设计模式
OSEFA运用了大量的设计模式来提供不同的可变性和灵活性。
其一为策略(Strategy)模式。对执行物料流动和零件加工的任务进行抽象,定义为抽象基类ProcessingStrategy
,其方法为process
。它也是框架识别的热点,将它从ProcessingControl
分离出来,就是分离变与不变,或者分离冻结点和热点。
对于策略基类和子类而言,虽说执行物料流动和零件加工的任务是热点,因为不同加工任务有着不同的物料流动和零件加工序列,但抽象出来的ProcessingStrategy
又表现了加工序列的共性,是不变的特征,又可以视为冻结点。
ProcessingStrategy
的子类执行的任务包括:
- 从货盘从单元仓库的位置x运送到缓冲区y
- 从位于z的货盘中取一个零件安装到机床n
- ……
要执行这些任务,无疑需要获取操作对象的一些信息,例如货盘的位置、缓冲区的编号等。然而,策略类是对行为的抽象,它自身是无状态的,不应该了解这些信息。正如书中所说:“ProcessingStrategy
应该仅仅描述物流和零件加工请求服务的先后顺序,而不应该为这些请求提供所有的参数。”
为此,框架将获取信息的职责从策略类中分离出去。该职责并没有交给调用策略类的ProcessingControl
,因为ProcessingControl
是整个任务执行的外部控制类,如果将这些职责交给它,就会让它的职责变得过于庞大。
框架的做法是引入调停者(Mediator)模式,通过定义ProcessingMediator
负责收集特定机器和设备的信息。其实现方式是由策略子类向调停者发送服务请求,再由调停者将策略需要的参数值添加到服务请求中,然后将请求转发给处理它们的领域对象。
对服务请求的处理则引入了命令(Command)模式。如果说策略封装了一种加工任务,则命令可以理解为是组成这些加工任务的原子或组合子任务,这样就有利于每个子任务的复用。策略和命令的职责是正交的,同时,策略对象又是命令对象的工厂。
OSEFA的主要开发者Schmid于1996年在Object-Oriented Programming杂志上发表了一篇论文design patterns for constructing the hot spots of a manufacturing framework,详细讲解了OSEFA框架对设计模式的运用。很可惜,我未曾找到这篇论文的原文,故而无法清晰地了解到详细的设计方案。不过,书中也给出了一些设计图帮助读者理解其中蕴含的设计原理,其设计大体如下图所示:
因为不了解框架细节,也不熟悉这个领域的业务知识,只能勉强分析其设计和实现的原理。
ProcessingControl
相当于热点子系统的唯一入口,它会调用调停者。调停者负责协调策略和多个领域对象之间的协作。
具体的任务执行者是每个命令的子类,命令子类的实现应该通过硬编码完成。但在加工制造领域,框架可以事先识别出主要的原子任务和组合任务,实现为命令子类后,作为构成策略的基础原材料。
策略可以通过硬编码完成,与之对应,还需要编写调停者类,因为对服务请求的处理逻辑与策略类是相匹配的。策略子类的process
方法封装的是加工序列,在已有命令类的基础上,加工序列可能只是对原子任务和组合任务的组装,不排除以配置方式实现的可能性。
不管策略类、调停类与命令类是否采用硬编码,由于它们封装了各自的职责,而又互相协作,只要它们已经实现在框架中,就可认为是支持复用的基本单元。对于框架的使用者而言,自然可以利用配置的方式完成对它们的组合,并按照框架规定的形式进行协作,而无需进行任何编码。
05结论
这样的低代码框架能带来什么价值呢?书中给出了事实数据:“用框架创建应用系统大约需要一天的时间。如果测试时间也计算在内,则需要数天。而使用单独项目开发控制软件的工作量大致在几个人月到一个人年之间。”
OSEFA并没有使用什么先进的技术(或许在当时,算是先进的设计),但其核心思想与当下许多低代码或无代码平台没有什么差别。
低代码就是一个新瓶,里面装的还是软件设计这么多年以来一直孜孜以求的目标:最大限度的复用能力与最小成本的扩展支持。
就目前的低代码平台而言,不过就是按照80/20定律(帕累托法则),尽可能将可复用的功能交给机器来做,减小开发的成本,并扩大它灵活开发的范围。这个目标不是软件行业一直追求的吗?
低代码不是什么新鲜玩意儿,只是在不同发展时期,设计方案所复用的基本单元不同罢了。从模块和对象的复用,到组件的复用,再到服务的复用;从基于元数据的SaaS开发,到通过拖拉拽即可完成数据分析的可视化BI工具,再到现在所谓的iPaaS和aPaaS,完全就是一脉相承,只不过现在发明了“低代码”这个名词罢了。
我并不反对低代码,相反,我很坚定地认为低代码平台会改变现有的IT生态。它既符合软件设计方法的发展趋势,我又何必逆向而行。通过偶然穿越到26年前,我寻觅到低代码早已存在的证据,不过想说明:低代码并没有这么神秘而神奇!