Join us today!

Decorator Design Pa...
 
Notifications
Clear all

Decorator Design Pattern

3 Posts
2 Users
4 Likes
682 Views
twinControls
Posts: 114
Admin
Topic starter
(@twincontrols)
Member
Joined: 2 years ago

In this tutorial, we will go through the power of object composition, which enable us to decorate our function blocks easily at runtime rather than having to make changes at compile time. The decorator design pattern is used to modify an object's functionality at runtime. When you learn this technique, you can give your objects new roles without modifying the base classes' source code.

To expand an object's behavior, we utilize inheritance, but this is done at compile time and is applicable to all instances of the class. Inheriting behavior through subclassing sets that behavior statically at compile time. Also, all subclasses must inherit the same behavior. However, if the behavior of an object can be extended through composition, it can be extended dynamically at runtime.  Decorator pattern comes into play to solve the problem of not being able to add any new functionality or remove any existing behavior at runtime. The decorator pattern adds additional responsibilities on an object. For additional functionalities, decorators offer a versatile alternative to subclassing.

By assembling objects dynamically, you can write new code to add new functionality instead of modifying existing code. Since this technique doesn't change existing code, it greatly reduces the possibility of bugs or unintended side effects. 

The decorator pattern class diagram: 

decoratorPatternUMLDiagram

 

Let's assume we are trying to draw different types of shapes and we want to be able to paint the border of these shapes with green color and change the border dash type. When we think about it the implementation, at first we can think about having base shape classes (e.g. Circle , Square, Rectange etc..) and subclasses (Green Circle, Green Square, Dash Border Green Square, Round Dot Border Rectangle etc.). The problem with this solution is that there might be many shape style subclasses and this would cause maintenance issues. Inheritance technique is powerful, however it doesn't always provide the most maintainable and flexible designs. 

Instead of overusing inheritance, we will have decorator function blocks, which will decorate our shape function blocks. We can think of these decorator function blocks as wrappers. 

According to the UML diagram above, we will have one I_Shape interface and our shape function blocks and the decorator function block will implement this interface. We will define a decorator function block as abstract so that each decorator function block has to implement their own draw shape methods. For demonstration purposes, we will have a green shape decorator and border dash type decorator. 

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

decoratorPatternClassDiagram

 

With this technique, we can easily wrap our shape function blocks and give them new functionalities easily. As an example, we will first draw a shape, then decorate (wrap) it with green border and change the border dash type. 

wrappers

 

Our sample project structure:

decoratorPatternProjectStructure

 

Let's add the I_Shape interface :

INTERFACE I_Shape

M_Draw: 

METHOD M_Draw : BOOL

 

Then we will have FB_Circle and FB_Square implementing the I_Shape interface. We are just printing the name of the shape in the M_Draw method of these function blocks. 

FB_Circle: 

FUNCTION_BLOCK FB_Circle IMPLEMENTS I_Shape

M_Draw: 

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

 

 

FB_Square:

FUNCTION_BLOCK FB_Square IMPLEMENTS I_Shape

M_Draw: 

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

 

 

We now need to create the abstract decorator function block. In the FB_init method, we will get the shape reference that user wants to decorate. 

FB_ShapeDecorator: 

FUNCTION_BLOCK ABSTRACT FB_ShapeDecorator IMPLEMENTS I_Shape
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
	_iDecoratedShape: I_Shape;
END_VAR

M_Draw: 

METHOD ABSTRACT M_Draw : BOOL

FB_init: 

METHOD FB_init : BOOL
VAR_INPUT
	bInitRetains : 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)
	iDecoratedShape : I_Shape;
END_VAR
_iDecoratedShape := iDecoratedShape;

 

 

Now we can extend the abstract decorator function block and create our concrete decorator function blocks. We will then add the decorating implementations in the M_Draw method. In the FB_init method, we will store the reference shape function block in the _iDecoratedShape variable that's declared in the parent function block. 

FB_GreenShapeDecorator:

FUNCTION_BLOCK FB_GreenShapeDecorator EXTENDS FB_ShapeDecorator

FB_init:

METHOD FB_init : BOOL
VAR_INPUT
	bInitRetains : 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)
	iDecoratedShape : I_Shape;
END_VAR
SUPER^._iDecoratedShape := iDecoratedShape;

 

M_SetGreenBorder: 

METHOD PRIVATE M_SetGreenBorder
VAR_INPUT
	iDecoratedShape : I_Shape;
END_VAR
ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Border Color : Green', '');

 

M_Draw: 

METHOD  M_Draw : BOOL
M_Draw := SUPER^._iDecoratedShape.M_Draw();
M_SetGreenBorder(SUPER^._iDecoratedShape);

 

In the M_Draw Method, we are first calling the M_Draw method of the reference shape function block and then the private decorating method (M_SetGreenBorder). 

 

FB_BorderDashTypeDecorator:

FUNCTION_BLOCK FB_BorderDashTypeDecorator EXTENDS FB_ShapeDecorator
VAR_INPUT
END_VAR
VAR_OUTPUT
END_VAR
VAR
	_eBorderDashType : E_BorderDashType;
END_VAR

 

In the FB_init method, we are getting the reference shape function block and the border dash type (E_BorderDashType) from the user. 

FB_init:

METHOD FB_init : BOOL
VAR_INPUT
	bInitRetains : 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)
	iDecoratedShape : I_Shape;
	eBorderDashType : E_BorderDashType;
END_VAR
SUPER^._iDecoratedShape := iDecoratedShape;
_eBorderDashType := eBorderDashType;

 

M_SetBorderDashType:

METHOD M_SetBorderDashType
VAR_INPUT
	iDecoratedShape : I_Shape;
END_VAR
CASE _eBorderDashType OF
	E_BorderDashType.Solid:
		ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Border Dash Type : Solid', '');
		
	E_BorderDashType.SquareDot:
		ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Border Dash Type : Square Dot', '');

	E_BorderDashType.RoundDot:
		ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Border Dash Type : Round Dot', '');
	
	E_BorderDashType.Dash:
		ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Border Dash Type : Dash', '');

	E_BorderDashType.LongDashDot:
		ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Border Dash Type :Long Dash Dot', '');
	
ELSE
	ADSLOGSTR(ADSLOG_MSGTYPE_HINT, 'Border Dash Type :Unkown', '');
END_CASE

 

M_Draw:

METHOD  M_Draw : BOOL
M_Draw := SUPER^._iDecoratedShape.M_Draw();
M_SetBorderDashType(SUPER^._iDecoratedShape);

 

We also added a property to be able to change the border dash type after initialization as well. 

PROPERTY P_GS_BorderDashType : E_BorderDashType

Get:

P_GS_BorderDashType := _eBorderDashType;

Set:

_eBorderDashType := P_GS_BorderDashType;

 

E_BorderDashType: 

{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_BorderDashType :
(
	Solid := 0,
	RoundDot,
	SquareDot,
	Dash,
	LongDashDot
);
END_TYPE

 

 

Now we can declare the function blocks in the MAIN program and initialize the decorator function blocks with the reference shape function blocks. 

PROGRAM MAIN
VAR
	fbCircle : FB_Circle;
	fbGreenCircle : FB_GreenShapeDecorator(fbCircle);
	fbRoundDotBorderGreenCircle : FB_BorderDashTypeDecorator(fbGreenCircle,E_BorderDashType.RoundDot);
	
	fbSquare : FB_Square;
	fbGreenSquare : FB_GreenShapeDecorator(fbSquare);
	fbDashBorderGreenSquare : FB_BorderDashTypeDecorator(fbGreenSquare,E_BorderDashType.Dash);
END_VAR
fbRoundDotBorderGreenCircle.M_Draw();

fbDashBorderGreenSquare.M_Draw();

 

We first declared a shape function block, then passed this function block to our first decorator function block(FB_GreenShapeDecorator). To modify the border dash type, we passed the first decorated function block to our second decorator function block.

When you call the fbRoundDotBorderGreenCircle.M_Draw() method, it first prints the shape name, then decorates it with green color, then wraps it with round dot dash border. 

decoratorPattern result

 

To comprehend this technique more, you can try adding more decorator function blocks and modifying the FB_GreenShapeDecorator function block to allow more color options. 

The Decorator pattern contains a set of decorator classes used to wrap concrete components and allows us to change the behavior of the components dynamically by adding new functionalities. A decorator class reflects the type of component it decorates and they are the same type of objects they decorate.  You can create as many as decorators you need and wrap a component with all of them if you like, however having many decorates in the design can complicate the project. 

 

Reply
2 Replies
rruiter
Posts: 63
(@rruiter)
Estimable Member
Joined: 2 years ago

Kudos for adding the "implements" and "aks" etc labels in the UML. I'm always confused by what each line means :D.

In case you don't like shapes, but do like pizza. There is another example of the decorator pattern.

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

Member
Posts: 114

@rruiter Thank you! Yes, it gets really confusing especially when the UML diagram is quite big. Love pizza!!! 🍕 But not when it is decorated with pineapple on it 😃

Reply
Share: