PLC结构化文本设计模式——原型模式(Prototype Pattern)

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_Interface

METHOD Dispose
VAR_INPUT
END_VAR

创建可克隆I_Cloneable接口,扩展于I_Disposable

INTERFACE I_Cloneable EXTENDS I_Disposable

METHOD Clone : I_Cloneable
VAR_INPUT
END_VAR

创建产品参数接口I_ProductParameter,继承I_Cloneable

INTERFACE I_ProductParameter EXTENDS I_Cloneable

PROPERTY 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
VAR
	sProductName 	: STRING;
	nProductId	 	: INT;
	nProductWidth	: REAL;
	nProductHeight	: REAL;
END_VAR
------
METHOD Clone : I_Cloneable
VAR
	pProductParameter : POINTER TO FB_ProductParameter;
END_VAR

pProductParameter := __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,内部成员无论是POINTERREFERENCE或者INTERFACE只是将fb2内的地址赋值给fb1,fb1里的引用类型变量地址指向的是fb2里的内存,也就是fb1和fb2引用类型使用的是同一块内存。(浅拷贝)

主程序运行,先对fbProductParameter属性赋值初始值,iProductParameterClone接收克隆对象实例,接着将接口iProductParameterClone转换成iProductParameter,日志记录克隆之后的属性值。最后再将iProductParameterClone接口转换成iProductParameterDispose释放创建的内存。

PROGRAM MAIN
VAR
	bTest1 : BOOL;
	iProductParameter 		: I_ProductParameter;
	fbProductParameter 		: FB_ProductParameter;
	iProductParameterClone	: I_Cloneable;
	iProductParameterDispose: I_Disposable;	
END_VAR

IF bTest1 THEN
	fbProductParameter.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_IF
	bTest1 := 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
posted @ 2025-09-16 08:33  J_Sheng  阅读(14)  评论(0)    收藏  举报