Join us today!
Abstract Factory Design Pattern
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:
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:
Project structure:
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.
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.
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:
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:
{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:
{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:
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.
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
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/
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... 👍 👍 👍
https://github.com/runtimevic
https://github.com/TcMotion
https://www.youtube.com/playlist?list=PLEfi_hUmmSjFpfdJ6yw3B9yj7dWHYkHmQ
https://github.com/VisualPLC
@runtimevictor Looks like he is using draw.io
https://drawio-app.com/
Honestly, I wouldn't use anything else tbh.
-Evan H.
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.
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/
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...
https://github.com/runtimevic
https://github.com/TcMotion
https://www.youtube.com/playlist?list=PLEfi_hUmmSjFpfdJ6yw3B9yj7dWHYkHmQ
https://github.com/VisualPLC
Hello,
in the output I get 2 lines from each message and I don't know why this happens?
attach image:
https://github.com/runtimevic
https://github.com/TcMotion
https://www.youtube.com/playlist?list=PLEfi_hUmmSjFpfdJ6yw3B9yj7dWHYkHmQ
https://github.com/VisualPLC
-
Decorator Design Pattern
2 years ago
-
Observer Design Pattern
2 years ago
-
Factory Method Design Pattern
2 years ago
- 17 Forums
- 267 Topics
- 924 Posts
- 0 Online
- 737 Members