Join us today!

Fluent Interface an...
 
Notifications
Clear all

Fluent Interface and Method Chaining in TwinCAT 3

16 Posts
8 Users
7 Reactions
2,177 Views
twinControls
Posts: 114
Admin
Topic starter
(@twincontrols)
Member
Joined: 2 years ago

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. 

I Calculate
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

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();

 

result

 

Everyone even non-programmers can easily grasp the code thanks to the fluent interface and method chaining.

 

 

Reply
15 Replies
2 Replies
(@joris)
Joined: 2 years ago

Eminent Member
Posts: 17

@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

 

Reply
twinControls
Admin
(@twincontrols)
Joined: 2 years ago

Member
Posts: 114

@joris Thank you! 

Alternatively, you can add the implicit 'Division Checks' for the whole application. 

implicitcheck

 

divisionchecks

 

 

Reply
rruiter
Posts: 63
(@rruiter)
Estimable Member
Joined: 2 years ago
Reply
benhar-dev
Posts: 17
(@benhar-dev)
Eminent Member
Joined: 2 years ago

Another example of a fluent CSV writer here

Reply
Posts: 4
(@serhatozyildiz)
New Member
Joined: 2 years ago

@twincontrols Thanks for this post. It is very useful and readable. I added properties to my Interface.

Capture

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;
Reply
7 Replies
twinControls
Admin
(@twincontrols)
Joined: 2 years ago

Member
Posts: 114

@serhatozyildiz 

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.... 

Reply
(@serhatozyildiz)
Joined: 2 years ago

New Member
Posts: 4

@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!

Reply
runtimevictor
(@runtimevictor)
Joined: 2 years ago

Estimable Member
Posts: 156

@serhatozyildiz ,

look at this GitHub organization,

I think you may be interested,

if you want to be part tell me...

https://github.com/TcMotion/Component_Motion_OOP_Axis

Reply
(@serhatozyildiz)
Joined: 2 years ago

New Member
Posts: 4

@runtimevictor Hello! I have checked the link above, but I can't find anything. It gives 404 error.

Reply
runtimevictor
(@runtimevictor)
Joined: 2 years ago

Estimable Member
Posts: 156

@serhatozyildiz ,

https://github.com/TcMotion

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...

 

Reply
(@serhatozyildiz)
Joined: 2 years ago

New Member
Posts: 4

@runtimevictor username: serhatozyildiz
Thank you!

Reply
runtimevictor
(@runtimevictor)
Joined: 2 years ago

Estimable Member
Posts: 156

@serhatozyildiz , 👍 😏 

Reply
Posts: 1
(@ddsouza1)
New Member
Joined: 2 years ago

 

Capture

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?

 

 

Reply
1 Reply
runtimevictor
(@runtimevictor)
Joined: 2 years ago

Estimable Member
Posts: 156

@ddsouza1 ,

You must return the method name

DecreaseBy := THIS^;

Reply
Page 1 / 2
Share: