Join us today!
Fluent Interface and Method Chaining in TwinCAT 3
The fluent interface design pattern makes your code more readable and simple. We can think of the Fluent Interface as a concept, whereas the method chaining is an implementation. The goal of the fluent interface design is to be able to apply multiple properties to an object by connecting the methods with dots(.) instead of having to apply to each method individually.
Let's say you want to initialize an axis by setting some of its properties and methods. You can achieve this using one line of code as below:
fbInitAxis.Name('Conveyor_1') .Enable(TRUE) .HomePosition(0) .Speed(10) .Accel(20) .Decel(15) .BelongsTo('Gantry_1');
By making the code understandable and fluid, the fluent interface gives you the impression that you are reading a sentence. To achieve this design pattern, you would need to use method chaining.
In this technique, each method returns an object and you can chain all the methods together.
Let's assume we need a function block that can implement multiple math operations. To demonstrate the fluent interface and method chaining, first we will create an interface called I_Calculate and we will set the return type of each method to this interface.
METHOD SetNumber : I_Calculate VAR_INPUT lNumber : LREAL; END_VAR
METHOD IncreaseBy : I_Calculate VAR_INPUT lIncreaseBy : REAL; END_VAR
METHOD DecreaseBy : I_Calculate VAR_INPUT lDecreaseBy : REAL; END_VAR
METHOD MultiplyBy : I_Calculate VAR_INPUT lMultiplyBy : REAL; END_VAR
METHOD DivideBy : I_Calculate VAR_INPUT lDivideBy : REAL; END_VAR
METHOD GetResult : LREAL
METHOD Clear : I_Calculate
As you can see above, each math operation method is set to return the I_Calculate interface.
Now we will create a function block called FB_Calculate which will implement the I_Calculate interface.
FB_Calculate:
FUNCTION_BLOCK FB_Calculate IMPLEMENTS I_Calculate VAR_INPUT END_VAR VAR_OUTPUT END_VAR VAR {attribute 'hide'} lResult : LREAL := 0; END_VAR
Method SetNumber:
METHOD SetNumber : I_Calculate VAR_INPUT lNumber : LREAL; END_VAR lResult := lNumber; SetNumber := THIS^;
Method IncreaseBy:
METHOD IncreaseBy : I_Calculate VAR_INPUT lIncreaseBy : REAL; END_VAR lResult := lResult + lIncreaseBy; IncreaseBy := THIS^;
Method DecreaseBy:
METHOD DecreaseBy : I_Calculate VAR_INPUT lDecreaseBy : REAL; END_VAR lResult := lResult - lDecreaseBy; DecreaseBy := THIS^;
Method MultiplyBy:
METHOD MultiplyBy : I_Calculate VAR_INPUT lMultiplyBy : REAL; END_VAR lResult := lResult * lMultiplyBy; MultiplyBy := THIS^;
Method DivideBy:
METHOD DivideBy : I_Calculate VAR_INPUT lDivideBy : REAL; END_VAR lResult := lResult / lDivideBy; DivideBy := THIS^;
Method GetResult:
METHOD GetResult : LREAL GetResult := lResult;
Method Clear:
METHOD Clear : I_Calculate MEMSET(destAddr:= ADR(lResult),fillByte :=0, n:=SIZEOF(lResult)); Clear := THIS^;
In the MAIN program;
PROGRAM MAIN VAR fMyResult : LREAL; fbCalculate : FB_Calculate; END_VAR
fMyResult := fbCalculate.Clear() .SetNumber(12) .DecreaseBy(2) .IncreaseBy(3.9) .MultiplyBy(5) .DivideBy(3) .GetResult();
Everyone even non-programmers can easily grasp the code thanks to the fluent interface and method chaining.
In case you want to say thank you !)
We'd be very grateful if you could share this community with your colleagues and friends. You can also buy us a coffee to keep us fueled 😊 This is the best way to say thank you to this project and support your community.
twinControls - https://twincontrols.com/
@twincontrols Thanks, it's very interresting.
I would like to add a piece of code to the divideBy method
METHOD DivideBy : I_Calculate VAR_INPUT lDivideBy : REAL; END_VAR VAR exc : __SYSTEM.ExceptionCode; END_VAR __TRY lResult := lResult / lDivideBy; __CATCH(exc) IF exc = __SYTEM.ExceptionCode.RTSEXCPT_FPU_DIVIDEBYZERO THEN lResult := lResult; // Or other result. END_IF __FINALLY DivideBy := THIS^; __ENDTRY
@joris Thank you!
Alternatively, you can add the implicit 'Division Checks' for the whole application.
In case you want to say thank you !)
We'd be very grateful if you could share this community with your colleagues and friends. You can also buy us a coffee to keep us fueled 😊 This is the best way to say thank you to this project and support your community.
twinControls - https://twincontrols.com/
Another example from PlcCoder.com.
My blog: https://cookncode.com/twincat
My code: https://github.com/roald87
Another example of a fluent CSV writer here
My blog: codingbytes
My code: https://github.com/benhar-dev
@twincontrols Thanks for this post. It is very useful and readable. I added properties to my Interface.
In method chaining, I can use properties and it works; (it is for example.)
IF bEnable THEN IF fbAxis.enableAxis(TRUE).isEnable THEN bEnable := FALSE; END_IF END_IF
But... I want to use like;
IF bReset THEN IF fbAxis.enableAxis(TRUE).isEnable.resetAxis(TRUE).isReset THEN fbAxis.resetAxis(FALSE); bReset := FALSE; END_IF END_IF
In this case, isEnable gives error. I can't add properties in chanining. How can I make this possible?
FUNCTION_BLOCK fbAxisMotion IMPLEMENTS I_Axis VAR AxisRef : Axis_Ref; fbMcPower : MC_Power; fbMcReset : MC_Reset; fbMcReset2 : MC_Reset; fbMcJog : MC_Jog; fbMcMove : MC_MoveAbsolute; END_VAR
METHOD enableAxis : I_Axis VAR_INPUT bEnable : BOOL; END_VAR fbMcPower( Axis := AxisRef, Enable := bEnable, Enable_Positive := bEnable, Enable_Negative := bEnable, Override := speedOverride, Status => );//bStatusEnable ); IF fbMcPower.Error THEN nErrorID := fbMcPower.ErrorID; END_IF enableAxis := THIS^;
isEnable := fbMcPower.Status;
Hi, welcome to the community!
You can add another method called getAxisStatus() which would return a DUT like ST_AxisStatus. Then you can use this struct for your if condition statements.
ST_AxisStatus.isReady, ST_AxisStatus.isResetSuccessful, ST_AxisStatus.Powered etc....
In case you want to say thank you !)
We'd be very grateful if you could share this community with your colleagues and friends. You can also buy us a coffee to keep us fueled 😊 This is the best way to say thank you to this project and support your community.
twinControls - https://twincontrols.com/
@twincontrols using DUT will work on method chaining? Like;
fbAxis.enableAxis(TRUE).isEnable.resetAxis(TRUE).isReset
power Axis. If it is powered, reset Axis. If it is reset....
So how can apply that DUT to this chain?
Thank you!
look at this GitHub organization,
I think you may be interested,
if you want to be part tell me...
https://github.com/runtimevic
https://github.com/TcMotion
https://www.youtube.com/playlist?list=PLEfi_hUmmSjFpfdJ6yw3B9yj7dWHYkHmQ
https://github.com/VisualPLC
@runtimevictor Hello! I have checked the link above, but I can't find anything. It gives 404 error.
https://github.com/TcMotion/Component_Motion_OOP_Axis
this repository is private, Give me your username or email GitHub and I'll send you an invitation...
https://github.com/runtimevic
https://github.com/TcMotion
https://www.youtube.com/playlist?list=PLEfi_hUmmSjFpfdJ6yw3B9yj7dWHYkHmQ
https://github.com/VisualPLC
@serhatozyildiz , 👍 😏
https://github.com/runtimevic
https://github.com/TcMotion
https://www.youtube.com/playlist?list=PLEfi_hUmmSjFpfdJ6yw3B9yj7dWHYkHmQ
https://github.com/VisualPLC
Hello guys,
I tried implementing the same solution what twinControls showed at the beginning. I am new to TWINCAT 3, and learning stuffs. I am getting this error. Can somebody point out, what is my mistake here?
https://github.com/runtimevic
https://github.com/TcMotion
https://www.youtube.com/playlist?list=PLEfi_hUmmSjFpfdJ6yw3B9yj7dWHYkHmQ
https://github.com/VisualPLC
-
Operator Overloading in TwinCAT using OOP
10 months ago
-
A few difficulties while learning OOP
1 year ago
-
Decorator Design Pattern
2 years ago
-
Dependency Injection in TwinCAT
2 years ago
-
Object Oriented Programming (OOP) resources
2 years ago
- 17 Forums
- 265 Topics
- 932 Posts
- 3 Online
- 688 Members