PLC Structured Text Design Patterns
PLC结构化文本设计模式——原型模式(Prototype Pattern)
介绍
原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种类型的设计模式属于创建型模式,这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。当直接创建对象的代价比较大时,则采用这种模式。——Java 原型模式|菜鸟教程
使用原型实例指定要创建对象的种类,并通过拷贝这些原型创建新的对象。与直接实例化类创建新对象不同,原型模式通过拷贝现有对象生成新对象——Java 原型模式|菜鸟教程
使用场景
-
当创建一个对象需要消耗大量资源(如数据库查询、网络请求、复杂计算),或构造过程非常复杂时,原型模式可以通过复制已有对象来避免重复的高成本操作。
-
当系统需要在运行时动态创建多种相似但略有差异的对象,且这些对象的类型可能无法提前预知时,原型模式可以通过克隆不同原型来快速生成新对象。
-
当需要创建对象但无法访问其构造函数(如构造函数为私有),或构造函数参数复杂难以获取时,原型模式可以通过克隆已有实例绕过这些限制。
Tips:对于PLC来说FB构造函数(FB_init)明确禁止私有。
-
当需要保存对象在不同阶段的状态(如撤销操作),原型模式可以通过克隆当前状态来实现历史记录的保存。
-
当需要创建大量结构相似、仅部分属性不同的对象时,原型模式可以通过克隆基准对象并修改差异部分,提高创建效率。
优缺点
- 优点
-
当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有的实例可以提高新实例的创建效率。
-
可以使用深复制的方式保存对象的状态。将对象复制一份并将其状态保存起来,以便于在使用的时候使用,比如恢复到某一个历史状态,可以辅助实现撤销操作。
-
- 缺点
-
需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了开闭原则。
-
为了支持深复制,当对象之间存在多重嵌套引用关系时,每一层对象都必须支持深复制,实现起来可能比较麻烦。
-
伪代码
创建基础接口类型,主要目的:为了后续接口类型转换,接口与接口和接口与指针之间的转换。
INTERFACE I_Interface EXTENDS __SYSTEM.IQueryInterface
创建可销毁/清除I_Disposable
接口类型,扩展于I_Interface
。
INTERFACE I_Disposable EXTENDS I_InterfaceMETHOD Dispose
VAR_INPUT
END_VAR
创建可克隆I_Cloneable
接口,扩展于I_Disposable
。
INTERFACE I_Cloneable EXTENDS I_DisposableMETHOD Clone : I_Cloneable
VAR_INPUT
END_VAR
创建产品参数接口I_ProductParameter
,继承I_Cloneable
。
INTERFACE I_ProductParameter EXTENDS I_CloneablePROPERTY Height : REAL
Get()
Set()PROPERTY ID : INT
Get()
Set()PROPERTY Name : STRING
Get()
Set()PROPERTY Width : REAL
Get()
Set()
定义FB_ProductParameter
类并实现I_ProductParameter
接口。使用PLC动态创建对象实例需要开发人员手动去管理对象的生命周期,使用完需要销毁/释放内存,这区别于C#
存在gc机制,自动回收内存。因此,需要实现方法Dispose
,释放内存防止资源浪费,使用__New()
创建对象实例时需要特别注意这一点。
{attribute 'enable_dynamic_creation'}
FUNCTION_BLOCK FB_ProductParameter IMPLEMENTS I_ProductParameter
VARsProductName : STRING;nProductId : INT;nProductWidth : REAL;nProductHeight : REAL;
END_VAR
------
METHOD Clone : I_Cloneable
VARpProductParameter : POINTER TO FB_ProductParameter;
END_VARpProductParameter := __NEW(FB_ProductParameter);
(* 以下操作均为"浅拷贝" *)
// 内部成员(变量)按个拷贝
pProductParameter^.Name := THIS^.sProductName;
pProductParameter^.ID := THIS^.nProductId;
pProductParameter^.Width := THIS^.nProductWidth;
pProductParameter^.Height := THIS^.nProductHeight;
// 或者直接赋值
// pProductParameter^ := THIS^;
Clone := pProductParameter^;
------
METHOD Dispose__DELETE(THIS);
ADSLOGSTR(msgCtrlMask :=ADSLOG_MSGTYPE_LOG, msgFmtStr :='Memory has released', strArg := '');
------
PROPERTY Height : REAL
Get:Height := THIS^.nProductHeight;
Set:THIS^.nProductHeight := Height;
------
PROPERTY ID : INT
Get:ID := THIS^.nProductId;
Set:THIS^.nProductId := ID;
------
PROPERTY Name : STRING
Get:Name := THIS^.sProductName;
Set:THIS^.sProductName := Name;
------
PROPERTY Width : REAL
Get:Width := THIS^.nProductWidth;
Set:THIS^.nProductWidth := Width;
浅拷贝(Shallow Copy)
定义:创建一个新对象,然后将原对象的字段值直接复制到新对象中。
对于值类型成员(如 int、float、struct 等):直接复制值本身,新对象和原对象的值类型成员相互独立。
对于引用类型成员(如类实例、数组等):只复制引用地址,新对象和原对象的引用类型成员指向同一个内存地址。
深拷贝(Deep Copy)
定义:创建一个新对象,然后递归复制原对象的所有成员,包括引用类型成员所指向的对象。
无论属性值是基本数据类型还是引用类型,都会创建一个完全独立的副本。
对于PLC而言若相同类型FB直接fb1:=fb2
,内部成员无论是POINTER
或REFERENCE
或者INTERFACE
只是将fb2内的地址赋值给fb1,fb1里的引用类型变量地址指向的是fb2里的内存,也就是fb1和fb2引用类型使用的是同一块内存。(浅拷贝)
主程序运行,先对fbProductParameter
属性赋值初始值,iProductParameterClone
接收克隆对象实例,接着将接口iProductParameterClone
转换成iProductParameter
,日志记录克隆之后的属性值。最后再将iProductParameterClone
接口转换成iProductParameterDispose
释放创建的内存。
PROGRAM MAIN
VARbTest1 : BOOL;iProductParameter : I_ProductParameter;fbProductParameter : FB_ProductParameter;iProductParameterClone : I_Cloneable;iProductParameterDispose: I_Disposable;
END_VARIF bTest1 THENfbProductParameter.ID := 1;fbProductParameter.Name := 'glasses';fbProductParameter.Width:= 800;fbProductParameter.Height:= 1000;iProductParameterClone := fbProductParameter.Clone();// 将拷贝创建的新对象转换成I_ProductParameter接口类型IF __QUERYINTERFACE(iProductParameterClone,iProductParameter) THEN// 输出拷贝对象成员值ADSLOGSTR(msgCtrlMask :=ADSLOG_MSGTYPE_LOG, msgFmtStr :='Name:%s', strArg := iProductParameter.Name);ADSLOGSTR(msgCtrlMask :=ADSLOG_MSGTYPE_LOG, msgFmtStr :='Id:%s', strArg := TO_STRING(iProductParameter.ID));ADSLOGSTR(msgCtrlMask :=ADSLOG_MSGTYPE_LOG, msgFmtStr :='Width:%s', strArg := TO_STRING(iProductParameter.Width));ADSLOGSTR(msgCtrlMask :=ADSLOG_MSGTYPE_LOG, msgFmtStr :='Height:%s', strArg := TO_STRING(iProductParameter.Height));END_IF// iProductParameter.Dispose();释放内存// 将拷贝的新对象转换成I_Disposable接口类型(主要演示__QUERYINTERFACE用法)IF __QUERYINTERFACE(iProductParameterClone,iProductParameterDispose) THEN// 将对象内存释放iProductParameterDispose.Dispose();END_IFbTest1 := FALSE;
END_IF
日志输出结果:
MSG | 'PlcTask' (350): Name:glasses
MSG | 'PlcTask' (350): Id:1
MSG | 'PlcTask' (350): Width:800.0
MSG | 'PlcTask' (350): Height:1000.0
MSG | 'PlcTask' (350): Memory has released