PLC Structured Text Design Patterns
PLC结构化文本设计模式——建造者模式(Builder Pattern)
介绍
建造者模式是一种创建型设计模式,它允许你创建复杂对象的步骤与表示方式相分离。
建造者模式是一种创建型设计模式,它的主要目的是将一个复杂对象的构建过程与其表示相分离,从而可以创建具有不同表示形式的对象。
——Java 建造者模式|菜鸟教程
使用场景
- 需要生成的对象具有复杂的内部结构。——Java 建造者模式|菜鸟教程
- 需要生成的对象内部属性相互依赖。——Java 建造者模式|菜鸟教程
与工厂模式的区别是:建造者模式更加关注于零件装配的顺序。——Java 建造者模式|菜鸟教程
优缺点
-
优点
分离构建过程和表示,使得构建过程更加灵活,可以构建不同的表示。
可以更好地控制构建过程,隐藏具体构建细节。
代码复用性高,可以在不同的构建过程中重复使用相同的建造者。
——Java 建造者模式|菜鸟教程 -
缺点
如果产品的属性较少,建造者模式可能会导致代码冗余。增加了系统的类和对象数量。
——Java 建造者模式|菜鸟教程
伪代码
下面代码示例是根据其它文章评论提出的问题而设计的。这里代码仅作学习参考,可能存在未知的Bug,勿照搬照套。
博主可以给个OOP的实际应用例子吗,光看的话根本想不到什么在plc应用上比较优点的地方,我的想法就是如果将一个时间输入拆分为时分秒的三个接口,比如1小时72min 65s 这种输入能够合并成一个 2h 13min 5s 这种比较规范的时间 ,但是并不能想出来在TC3、codesys中的OOP 程序实现该如何写
INTERFACE I_Interface EXTENDS __SYSTEM.IQueryInterface
创建时间构造器接口,方法GetTime
将设置的时间转换成返回FB_Time
时间类型。方法Reset
将内部时间字段时、分、秒存储的数据复位清空,其余方法SetHours
、SetMinutes
、SetSeconds
用于设置时间。
INTERFACE I_TimeBuilder EXTENDS I_Interface
------
METHOD GetTime : FB_Time
VAR_INPUT
END_VAR
------
METHOD Reset : HRESULT
VAR_INPUT
END_VAR
------
METHOD SetHours : HRESULT
VAR_INPUThours : UINT;
END_VAR
------
METHOD SetMinutes : HRESULT
VAR_INPUTminutes : UINT;
END_VAR
------
METHOD SetSeconds : HRESULT
VAR_INPUTseconds : UINT;
END_VAR
创建时间FB_Time
类,用于存储相关的时间数据。
FUNCTION_BLOCK FB_Time
VARhour : UINT; // 时minute : UINT; // 分second : UINT; // 秒
END_VAR
------
METHOD Clear : HRESULT
VAR_INPUT
END_VARhour := 0;
minute := 0;
second := 0;
------
PROPERTY P_Hours : UINT
Get:P_Hours := hour;
Set:hour := P_Hours;
------
PROPERTY P_Minutes : UINT
Get:P_Minutes := minute;
Set:minute := P_Minutes;
------
PROPERTY P_Seconds : UINT
Get:P_Seconds := second;
Set:second := P_Seconds;
------
METHOD ToTimeString : STRING
VARsTemp : STRING;
END_VAR// 拼接字符串,拼接格式:时:分:秒
sTemp := CONCAT(TO_STRING(hour),':');
sTemp := CONCAT(sTemp,TO_STRING(minute));
sTemp := CONCAT(sTemp,':');
sTemp := CONCAT(sTemp,TO_STRING(second));
ToTimeString := sTemp;
ADSLOGSTR(msgCtrlMask :=ADSLOG_MSGTYPE_LOG, msgFmtStr :='ToTimeString:%s', strArg := sTemp);
创建FB_TimeBuilder
时间构造器,并实现接口I_TimeBuilder
,其中SetHours
、SetMinutes
、SetSeconds
将输入的整型时间转换成时分秒格式。
FUNCTION_BLOCK FB_TimeBuilder IMPLEMENTS I_TimeBuilder
VARfbTime : FB_Time;
END_VAR
------
METHOD GetTime : FB_Time
VAR_INPUT
END_VARGetTime := THIS^.fbTime;
------
METHOD Reset : HRESULT
VAR_INPUT
END_VARfbTime.Clear();
------
METHOD SetHours : HRESULT
VAR_INPUThours : UINT;
END_VARfbTime.P_Hours := fbTime.P_Hours + hours;
------
METHOD SetMinutes : HRESULT
VAR_INPUTminutes : UINT;
END_VAR
VARtemp : UINT;
END_VAR// 取余数
temp := minutes / 60;
// 满60分钟累加到小时
IF temp > 0 THENfbTime.P_Hours := fbTime.P_Hours + temp;
END_IF
//
fbTime.P_Minutes := fbTime.P_Minutes + minutes MOD 60;
------
METHOD SetSeconds : HRESULT
VAR_INPUTseconds : UINT;
END_VAR
VARtemp : UINT;
END_VARtemp := seconds / 60;
IF temp > 0 THENfbTime.P_Minutes := fbTime.P_Minutes + temp;
END_IF
fbTime.P_Seconds := seconds MOD 60;
创建FB_TimeDirector
指挥者/指导者,负责调用建造者的方法来构建产品,指导者并不了解具体的构建过程,只关心产品的构建顺序和方式。
FUNCTION_BLOCK FB_TimeDirector
VAR_iTimeBuilder : I_TimeBuilder;
END_VAR
------
METHOD FB_init : BOOL
VAR_INPUTbInitRetains : BOOL; // if TRUE, the retain variables are initialized (warm start / cold start)bInCopyCode : BOOL; // if TRUE, the instance afterwards gets moved into the copy code (online change)iTimeBuilder : I_TimeBuilder;
END_VAR_iTimeBuilder := iTimeBuilder;
------
METHOD Construct : FB_Time
VAR_INPUThour : UINT; // 时minute : UINT; // 分second : UINT; // 秒
END_VAR_iTimeBuilder.Reset();
_iTimeBuilder.SetSeconds(second);
_iTimeBuilder.SetMinutes(minute);
_iTimeBuilder.SetHours(hour);
Construct := _iTimeBuilder.GetTime();
方法Construct
内部,执行次序分别是Reset
、SetSeconds
、SetMinutes
、SetHours
确保每次构造时间先清空_iTimeBuilder
实例内部数据,然后以秒-分-时的次序累加计算。
主程序中运行测试,在方法Construct
参数内输入1小时72分65秒,并声明对应变量接收结果:2小时13分5秒。
| 'PlcTask' (350): ToTimeString:2:13:5
PROGRAM MAIN
VARfbTimeBuilder : FB_TimeBuilder;fbTimeDirector : FB_TimeDirector(fbTimeBuilder);fbTime : FB_Time;sTime : STRING;
END_VARfbTime := fbTimeDirector.Construct(1,72,65);
sTime := fbTime.ToTimeString();
至此,建造者示例完成,上述代码中可以根据实际需要更改,比如:Construct输入的是一个字符串,那么内部就需要对字符串解析。还有FB_Time
内的ToTimeString
方法也可以改成输出自定义结构体类型等。这些都不重要,感兴趣自行实现。
示例代码中某些方法实现不够优雅,ToTimeString
方法实现只是将一行sTemp := CONCAT(TO_STRING(hour),CONCAT(':',CONCAT(TO_STRING(minute),CONCAT(':',TO_STRING(second)))));
拆分成下面这种,代码可读性虽然强了一点,但是还不够强,接下来对下面代码进行优化。
METHOD ToTimeString : STRING
VARsTemp : STRING;
END_VARsTemp := CONCAT(TO_STRING(hour),':');
sTemp := CONCAT(sTemp,TO_STRING(minute));
sTemp := CONCAT(sTemp,':');
sTemp := CONCAT(sTemp,TO_STRING(second));
ToTimeString := sTemp;
同样这段代码也需要优化。
METHOD Construct : FB_Time
VAR_INPUThour : UINT; // 时minute : UINT; // 分second : UINT; // 秒
END_VAR_iTimeBuilder.Reset();
_iTimeBuilder.SetSeconds(second);
_iTimeBuilder.SetMinutes(minute);
_iTimeBuilder.SetHours(hour);Construct := _iTimeBuilder.GetTime();
下面将对FB_Time
和FB_TimeBuilder
进行优化,采用Fluent编程方式实现。首先将I_TimeBuilder
接口某些方法的返回值改成I_TimeBuilder
INTERFACE I_TimeBuilder EXTENDS I_Interface
------
METHOD GetTime : FB_Time
VAR_INPUT
END_VAR
------
METHOD Reset : I_TimeBuilder
VAR_INPUT
END_VAR
------
METHOD SetHours : I_TimeBuilder
VAR_INPUThours : UINT;
END_VAR
------
METHOD SetMinutes : I_TimeBuilder
VAR_INPUTminutes : UINT;
END_VAR
------
METHOD SetSeconds : I_TimeBuilder
VAR_INPUTseconds : UINT;
END_VAR
修改FB_TimeBuilder
方法实现,主体实现没有变,只是增加了方法名:=THIS^,也就是方法的返回值指向自身实例。
FUNCTION_BLOCK FB_TimeBuilder IMPLEMENTS I_TimeBuilder
VARfbTime : FB_Time;
END_VAR
------
METHOD GetTime : FB_Time
VAR_INPUT
END_VARGetTime := THIS^.fbTime;
------
METHOD Reset : I_TimeBuilder
VAR_INPUT
END_VARfbTime.Clear();
Reset := THIS^;
------
METHOD SetHours : I_TimeBuilder
VAR_INPUThours : UINT;
END_VARfbTime.P_Hours := fbTime.P_Hours + hours;
SetHours := THIS^;
------
METHOD SetMinutes : I_TimeBuilder
VAR_INPUTminutes : UINT;
END_VAR
VARtemp : UINT;
END_VAR// 取余数
temp := minutes / 60;
// 满60分钟累加到小时
IF temp > 0 THENfbTime.P_Hours := fbTime.P_Hours + temp;
END_IF
fbTime.P_Minutes := fbTime.P_Minutes + minutes MOD 60;SetMinutes := THIS^;
------
METHOD SetSeconds : I_TimeBuilder
VAR_INPUTseconds : UINT;
END_VAR
VARtemp : UINT;
END_VARtemp := seconds / 60;
IF temp > 0 THENfbTime.P_Minutes := fbTime.P_Minutes + temp;
END_IF
fbTime.P_Seconds := seconds MOD 60;SetSeconds := THIS^;
新增接口I_StringBuilder
字符串构造器,用来构造时间格式化字符串。
INTERFACE I_StringBuilder EXTENDS I_Interface
------
METHOD Append : I_StringBuilder
VAR_INPUTsObject : STRING;
END_VAR
------
METHOD Clear : I_StringBuilder
VAR_INPUT
END_VAR
------
METHOD ToString : STRING
VAR_INPUT
END_VAR
创建FB_StringBuilder
字符串构造器实体类,实现I_StringBuilder
接口。
FUNCTION_BLOCK FB_StringBuilder IMPLEMENTS I_StringBuilder
VARsContent : STRING;
END_VAR
------
METHOD Append : I_StringBuilder
VAR_INPUTsObject : STRING;
END_VARsContent := CONCAT(sContent,sObject);
Append := THIS^;
------
METHOD Clear : I_StringBuildersContent := '';
Clear := THIS^;
------
METHOD ToString : STRINGToString := sContent;
FB_Time
类只更改ToTimeString
方法,其它成员均保持不变。
FUNCTION_BLOCK FB_Time
VARhour : UINT; // 时minute : UINT; // 分second : UINT; // 秒fbStringBuilder : FB_StringBuilder;
END_VAR
------
METHOD ToTimeString : STRING// 拼接字符串
ToTimeString := fbStringBuilder.Clear().Append(TO_STRING(hour)).Append(':').Append(TO_STRING(minute)).Append(':').Append(TO_STRING(second)).ToString();
ADSLOGSTR(msgCtrlMask :=ADSLOG_MSGTYPE_LOG, msgFmtStr :='ToTimeString:%s', strArg := ToTimeString);
FB_TimeDirector
类只更改Construct
方法,其它成员均保持不变。
FUNCTION_BLOCK FB_TimeDirector
VAR_iTimeBuilder : I_TimeBuilder;
END_VAR
------
METHOD Construct : FB_Time
VAR_INPUThour : UINT; // 时minute : UINT; // 分second : UINT; // 秒
END_VAR_iTimeBuilder.Reset().SetSeconds(second).SetMinutes(minute).SetHours(hour);Construct := _iTimeBuilder.GetTime();
主程序MAIN
未作任何修改,直接运行程序,输出结果保持与优化之前一致。
| 'PlcTask' (350): ToTimeString:2:13:5
至此本章结束,此篇刚好补齐之前PLC结构化文本(ST)——指针和引用(Pointer&Reference)
章节内提及的流式编程,接口方法的实现