Join us today!

Abstract Factory De...
 
Notifications
Clear all

Abstract Factory Design Pattern

6 Posts
3 Users
5 Reactions
982 Views
twinControls
Posts: 114
Admin
Topic starter
(@twincontrols)
Member
Joined: 2 years ago

In the factory method design pattern, we have explained how we can create a 'Creator' function block which would return the user desired function block using an interface. If you haven't read that tutorial, we would advise checking it first since we will continue the same project to explain the abstract factory pattern. 

 In the abstract factory pattern, a super-factory that generates more factories is the center of abstract factory patterns. Another name for this facility is the factory of factories. This kind of design pattern falls under the category of a creational pattern because it offers one of the finest ways to make an object.

A factory of related objects is created through an interface using the abstract factory pattern without the need to explicitly declare the classes of the objects. According to the Factory pattern, each produced factory can deliver objects.

Abstract factory class diagram: 

abstract factory

 

In the factory method pattern tutorial, we have created the FB_ShapeCreator function block which was responsible for instantiating the individual shape function blocks to draw the requested the shape. Now, we will need to paint those shapes with different colors. To achieve this, we will add another creator function block called 'FB_ColorCreator' which will be responsible for instantiating color function blocks. FB_Black, FB_Red and FB_Yellow color function blocks will implement the same interface called I_Color. 

To manage all the creator function blocks in our design, we will have an abstract factory function block called 'FB_Abstract Factory' without any implementation in it. FB_ShapeCreator and FB_ColorCreator will extend this function block. We can think of this function block as interface in our sample application.

We will also add a function block called 'FB_FactoryProducer' and it will have a method called 'M_GetFactory' which would return 'REFERENCE TO FB_Abstract Factory'. In its body, it will implement the logic for creating the requested factory. 

In the MAIN program, we will ask FB_FactoryProducer to create a factory for us. In this case, it will be either FB_ColorCreator or FB_ShapeCreator.  We will then be able to get the desired shape or color function block and execute the methods of this function block via interface. 

 

The class diagram for our sample application can be seen below: 

abstract factory application

 

Project structure: 

program structure abstract factory

 

Let's start with creating the enumerations first: 

E_Factory: 

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_Factory :
(
	init := 0,
	Shape,
	Color,
	end
);
END_TYPE

 

E_Color:

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_Color :
(
	init :=0,
	Black ,
	Red,
	Yellow,
	end
);
END_TYPE

 

E_Shape:

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_Shape :
(
	init := 0,
	Circle,
	Rectangle,
	Square,
	Star,
	Triangle,
	end
);
END_TYPE

 

Now, we will add the interfaces and the shape and color function blocks which will implement these interfaces:

I_Shape: 

INTERFACE I_Shape

 

Add a method called 'M_Draw'. Each shape function block will use this method to control the motion and draw the requested shape. 

M_Draw: 

METHOD M_Draw : BOOL;
VAR_INPUT
END_VAR

 

Add the function blocks below, which implement the I_Shape interface.

shapes

 

Add the {attribute 'enable_dynamic_creation'} on the top of the each function block's declaration section. This will enable us to instantiate the each function block dynamically using _NEW operator in the FB_ShapeCreator function block.

FB_Circle: 

{attribute 'enable_dynamic_creation'}
FUNCTION_BLOCK FB_Circle IMPLEMENTS I_Shape
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR

M_Draw method of FB_Circle: 

METHOD M_Draw : BOOL
ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Draw circle', '');
M_Draw := TRUE;

For demonstration purposes , we are just logging a message. Repeat this step for other shape function blocks and modify the message in accordance with the name of the shape.

 

I_Color: 

INTERFACE I_Color

Add a method called 'M_Fill'. Each color function block will use this method to control the motion and paint the requested shape. 

METHOD M_Fill : BOOL
VAR_INPUT
END_VAR

 

Add the function blocks below, which implement the I_Color interface.

colors

 

Add the {attribute 'enable_dynamic_creation'} on the top of the each function block's declaration section. This will enable us to instantiate the each function block dynamically using _NEW operator in the FB_ColorCreator function block.

FB_Black: 

{attribute 'enable_dynamic_creation'}
FUNCTION_BLOCK FB_Black IMPLEMENTS I_Color
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR

M_Fill method of FB_Black: 

METHOD M_Fill : BOOL
ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Paint Black', '');
M_Fill := TRUE;

For demonstration purposes , we are just logging a message. Repeat this step for other color function blocks and modify the message in accordance with the color name.

 

Now we will add the abstract factory function block which will have 3 methods; M_GetColorObject, M_GetShapeObject and M_ReleaseMemory. FB_ShapeCreator and FB_ColorCreator function blocks will extend this function block and implement the logic for the methods. 

FB AbstractFactory

 

FB_AbstractFactory

FUNCTION_BLOCK ABSTRACT FB_AbstractFactory
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
END_VAR

 

M_GetColorObject: 

METHOD  M_GetColorObject : I_Color
VAR_INPUT
	eColor : E_Color;
END_VAR

This method returns the I_Color interface so that the user in the main application can execute the M_Fill method through the I_Color interface. FB_ColorCreator will implement the logic for this method and return the requested color function block. 

 

M_GetShapeObject:

METHOD  M_GetShapeObject : I_Shape
VAR_INPUT
	eShape : E_Shape;
END_VAR

This method returns the I_Shape interface so that the user in the main application can execute the M_Draw method through the I_Shape interface. FB_ShapeCreator will implement the logic for this method and return the requested shape function block. 

 

M_ReleaseMemory:

METHOD ABSTRACT M_ReleaseMemory : BOOL
VAR_INPUT
END_VAR

The derived function blocks will have to include this method since we have declared it as abstract. FB_ShapeCreator and FB_ColorCreator will use this method to release the dynamically allocated memory for instantiating the individual color and shape function blocks. 

 

We can now add the creator function blocks; FB_ShapeCreator and FB_ColorCreator: 

FB_ShapeCreator:

FB ShapeCreator

 

{attribute 'hide_all_locals'}
{attribute 'enable_dynamic_creation'}
FUNCTION_BLOCK FB_ShapeCreator EXTENDS FB_AbstractFactory
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
      pCircle    : POINTER TO FB_Circle;
      pRectangle : POINTER TO FB_Rectangle;
      pSquare    : POINTER TO FB_Square;
      pStar      : POINTER TO FB_Star;
      pTriangle   : POINTER TO FB_Triangle;
      iShape : I_Shape;
END_VAR

 

M_GetShapeObject: 

METHOD M_GetShapeObject : I_Shape
VAR_INPUT
	eShape : E_Shape;
END_VAR
CASE eShape OF
	E_Shape.Circle:
		
		//Dynamic instantiation  
		pCircle := __NEW(FB_Circle);
		iShape := pCircle^;
		
		//Return the object
		IF iShape <> 0 THEN
			M_GetShapeObject := iShape;
		END_IF
		
		eShape := E_Shape.init;
	
	E_Shape.Rectangle:
		pRectangle := __NEW(FB_Rectangle);
		iShape := pRectangle^;
		
		//Return the object
		IF iShape <> 0 THEN
			M_GetShapeObject := iShape;
		END_IF
		
		eShape := E_Shape.init;	
		
	E_Shape.Square:
		pSquare := __NEW(FB_Square);
		iShape := pSquare^;
		
		//Return the object
		IF iShape <> 0 THEN
			M_GetShapeObject := iShape;
		END_IF
		
		eShape := E_Shape.init;	

	E_Shape.Star:
		pStar := __NEW(FB_Star);
		iShape := pStar^;
		
		//Return the object
		IF iShape <> 0 THEN
			M_GetShapeObject := iShape;
		END_IF

		eShape := E_Shape.init;	
	
	E_Shape.Triangle:
		pTriangle := __NEW(FB_Triangle);
		iShape := pTriangle^;
		
		//Return the object
		IF iShape <> 0 THEN
			M_GetShapeObject := iShape;
		END_IF

		eShape := E_Shape.init;	
END_CASE

 

If we look at this logic, we are first checking which shape is requested by the user, then we are dynamically creating the requested shape function block instance and returning this object to the user.

In the FB_Exit and M_ReleaseMemory method of FB_ShapeCreator, we are deleting all the pointers. FB_exit will be called implicitly, for example when downloading. 

FB_Exit: 

METHOD FB_exit : BOOL
VAR_INPUT
	bInCopyCode : BOOL; // if TRUE, the exit method is called for exiting an instance that is copied afterwards (online change).
END_VAR
IF (pCircle <> 0) THEN
  __DELETE(pCircle);
  pCircle := 0;
END_IF

IF (pRectangle <> 0) THEN
  __DELETE(pRectangle);
  pRectangle := 0;
END_IF

IF (pSquare <> 0) THEN
  __DELETE(pSquare);
  pSquare := 0;
END_IF

IF (pStar <> 0) THEN
  __DELETE(pStar);
  pStar := 0;
END_IF

IF (pTriangle <> 0) THEN
  __DELETE(pTriangle);
  pTriangle := 0;
END_IF

 

Place the same logic for M_ReleaseMemory as well: 

METHOD M_ReleaseMemory : BOOL
VAR_INPUT
END_VAR


IF (pCircle <> 0) THEN
  __DELETE(pCircle);
  pCircle := 0;
END_IF

IF (pRectangle <> 0) THEN
  __DELETE(pRectangle);
  pRectangle := 0;
END_IF

IF (pSquare <> 0) THEN
  __DELETE(pSquare);
  pSquare := 0;
END_IF

IF (pStar <> 0) THEN
  __DELETE(pStar);
  pStar := 0;
END_IF

IF (pTriangle <> 0) THEN
  __DELETE(pTriangle);
  pTriangle := 0;
END_IF

 

FB_ColorCreator:

FB ColorCreator
{attribute 'hide_all_locals'}
{attribute 'enable_dynamic_creation'}
FUNCTION_BLOCK FB_ColorCreator EXTENDS FB_AbstractFactory
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
      pBlack    : POINTER TO FB_Black;
      pRed 		: POINTER TO FB_Red;
      pYellow 	: POINTER TO FB_Yellow;
      iColor 	: I_Color;
END_VAR

 

M_GetColorObject: 

METHOD  M_GetColorObject : I_Color
VAR_INPUT
	eColor : E_Color;
END_VAR
CASE eColor OF
	E_Color.Black:
		
		//Dynamic instantiation  
		pBlack := __NEW(FB_Black);
		iColor := pBlack^;
		
		//Return the object
		IF iColor <> 0 THEN
			M_GetColorObject := iColor;
		END_IF
		
		eColor := E_Color.init;
	
		
	E_Color.Red:
		
		//Dynamic instantiation  
		pRed := __NEW(FB_Red);
		iColor := pRed^;
		
		//Return the object
		IF iColor <> 0 THEN
			M_GetColorObject := iColor;
		END_IF

		eColor := E_Color.init;
	
	E_Color.Yellow:
		
		//Dynamic instantiation  
		pYellow := __NEW(FB_Yellow);
		iColor := pYellow^;
		
		//Return the object
		IF iColor <> 0 THEN
			M_GetColorObject := iColor;
		END_IF

		eColor := E_Color.init;	
		
END_CASE

 

In this logic, we are first checking which color is requested by the user, then we are dynamically creating the requested color function block instance and returning this object to the user.

In the FB_Exit and M_ReleaseMemory method of FB_ColorCreator, we are deleting all the pointers. FB_exit will be called implicitly, for example when downloading. 

FB_Exit: 

METHOD FB_exit : BOOL
VAR_INPUT
	bInCopyCode : BOOL; // if TRUE, the exit method is called for exiting an instance that is copied afterwards (online change).
END_VAR
//Release the memory
IF (pBlack <> 0) THEN
	__DELETE(pBlack);
	pBlack := 0;
END_IF

//Release the memory
IF (pRed <> 0) THEN
	__DELETE(pRed);
	pRed := 0;
END_IF

//Release the memory
IF (pYellow <> 0) THEN
	__DELETE(pYellow);
	pYellow := 0;
END_IF

 

Place the same logic for M_ReleaseMemory as well: 

METHOD  M_ReleaseMemory : BOOL
VAR_INPUT
END_VAR
//Release the memory
IF (pBlack <> 0) THEN
	__DELETE(pBlack);
	pBlack := 0;
END_IF

//Release the memory
IF (pRed <> 0) THEN
	__DELETE(pRed);
	pRed := 0;
END_IF

//Release the memory
IF (pYellow <> 0) THEN
	__DELETE(pYellow);
	pYellow := 0;
END_IF

 

It's time to add the FB_FactoryProducer function block now. This function block will have a method called M_GetFactory which will return a reference to FB_AbstractFactory. 

FB FactoryProducer

 

FB_FactoryProducer

FUNCTION_BLOCK  FB_FactoryProducer
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
	pColorFactoy : POINTER TO FB_ColorCreator;
	pShapeFactory : POINTER TO FB_ShapeCreator;
END_VAR

 

M_GetFactory: 

METHOD M_GetFactory : REFERENCE TO FB_AbstractFactory
VAR_INPUT
	eFactory : E_Factory;
END_VAR
CASE eFactory OF
	E_Factory.Color:
	
		pColorFactoy := __NEW(FB_ColorCreator);
		IF pColorFactoy <> 0 THEN
			M_GetFactory REF= (pColorFactoy^);
		END_IF
	
		eFactory := E_Factory.init;
	
	E_Factory.Shape:
	
		pShapeFactory := __NEW(FB_ShapeCreator);
		IF pShapeFactory <> 0 THEN
			M_GetFactory REF= (pShapeFactory^);
		END_IF

		eFactory := E_Factory.init;
	
END_CASE

 

We are first checking which creator function block is requested by the user. We are then creating this function block dynamically and returning it using the allocation operator REF= . User in the main application will be able to request the factory needed using this method. 

 

In the FB_Exit and M_ReleaseMemory, we are deleting the dynamically allocated creator function blocks. 

FB_Exit:

METHOD FB_exit : BOOL
VAR_INPUT
	bInCopyCode : BOOL; // if TRUE, the exit method is called for exiting an instance that is copied afterwards (online change).
END_VAR
IF pColorFactoy <> 0 THEN
	__DELETE(pColorFactoy);
	pColorFactoy := 0;
END_IF

IF pShapeFactory <> 0 THEN
	__DELETE(pShapeFactory);
	pShapeFactory := 0;
END_IF

 

Place the same logic for M_ReleaseMemory as well. 

METHOD M_ReleaseMemory : BOOL
VAR_INPUT
END_VAR
IF pColorFactoy <> 0 THEN
	__DELETE(pColorFactoy);
	pColorFactoy := 0;
END_IF

IF pShapeFactory <> 0 THEN
	__DELETE(pShapeFactory);
	pShapeFactory := 0;
END_IF

 

 

We are done with designing our project. Now in the MAIN program, we can request a creator function block and execute the M_Draw or M_Fill command. 

MAIN: 

VAR
	fbFactoryProducer : FB_FactoryProducer;
	fbFactory : REFERENCE TO FB_AbstractFactory;
	
	iShape : I_Shape;
	iColor : I_Color;
	
	eShape : E_Shape := E_Shape.Star;
	eColor : E_Color := E_Color.Red;
	
	bStart: BOOL;
END_VAR

 

IF bStart THEN
	//Draw
	fbFactory REF= fbFactoryProducer.M_GetFactory(eFactory := E_Factory.Shape);
	
	IF __ISVALIDREF(fbFactory) THEN
		iShape := fbFactory.M_GetShapeObject(eShape:=eShape);
	END_IF
	
	IF iShape <> 0 THEN
		iShape.M_Draw();
	END_IF
	
	//Fill
	fbFactory REF= fbFactoryProducer.M_GetFactory(eFactory := E_Factory.Color);
	
	IF __ISVALIDREF(fbFactory) THEN
		iColor := fbFactory.M_GetColorObject(eColor:=eColor);
	END_IF
	
	IF iColor <> 0 THEN
		iColor.M_Fill();
	END_IF
	
	//Release Memory
	fbFactoryProducer.M_ReleaseMemory();
	
	bStart := FALSE;
END_IF

 

We are creating a shape factory first and then we are getting the desired shape function block. Using the M_Draw method through the I_Shape interface we are drawing the desired shape. 

To paint this shape, we are then creating the color factory and requesting the desired color. Finally we are executing the M_Fill command. When we are done with drawing and painting, we are executing M_ReleaseMemory of fbFactoryProducer which would execute the FB_Exit method of other function blocks. 

Set to bStart to TRUE to run the logic. 

draw paint result

Since we the initial values of eShape and eColor have been declared as Star and Red, we have drawn a star, then filled it with red color. 

 

Alternatively, you can chain the methods and get the same result: 

IF bStart THEN
	fbFactoryProducer.M_GetFactory(eFactory := E_Factory.Shape).M_GetShapeObject(eShape:=eShape).M_Draw();
	fbFactoryProducer.M_GetFactory(eFactory := E_Factory.Color).M_GetColorObject(eColor:=eColor).M_Fill();
	
	//Release Memory
	fbFactoryProducer.M_ReleaseMemory();
	bStart := FALSE;
END_IF

 

draw paint result2

 

 

Reply
5 Replies
4 Replies
runtimevictor
(@runtimevictor)
Joined: 2 years ago

Estimable Member
Posts: 156

@twincontrols ,

Hello, thank you very much for the new design pattern with this we can take our TwinCAT programs to the next level..., 🤩 

How do you make the UML ClassDiagram fit the lines so well?

it's great that publications about the Design pattern are made... 👍 👍 👍

Reply
ehixenbaugh
(@ehixenbaugh)
Joined: 2 years ago

Trusted Member
Posts: 33

@runtimevictor Looks like he is using draw.io 
https://drawio-app.com/

Honestly, I wouldn't use anything else tbh.

-Evan H.

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

Member
Posts: 114

@runtimevictor 

Hi, You're welcome! And thank you so much for your nice words! 

It's the built in class diagram feature in TwinCAT 3. I just spend a lot of time fixing the lines 😆 

You can use lucid chart or draw io for creating flow charts, diagrams. 

Reply
runtimevictor
(@runtimevictor)
Joined: 2 years ago

Estimable Member
Posts: 156

@twincontrols ,

you can export the ClassDiagram to a *.TcCD file, do you know if it can be imported into any other software? or do something with this file? I can't even import it into TwinCAT...

 

Reply
runtimevictor
Posts: 156
(@runtimevictor)
Estimable Member
Joined: 2 years ago

Hello,

in the output I get 2 lines from each message and I don't know why this happens?

attach image:

Design Patterns Creational Abstract Factory two lines
Reply
Share: