Inhalte

Common Practices: Data Structures vs Objects

Table of Contents

Introduction

There is a trivial but big difference between objects and data structures:

  • Objects hide their data and show their functions (methods)

  • Data structures show their data, but they have no functions

The difference seems trivial, but it has far-reaching consequences for maintainability.

  • Code built on top of data structures makes it easy to add new functions, but it makes it hard to customize data because existing functions have to be customized as well.

  • OOP makes it easy to add new classes without having side effects on existing functions. On the other hand, OOP makes it hard to add new functions to classes because whole class structures have to be adapted.

Mostly only the data changes, but not the functionality, and that's why we mostly use objects instead of type structs.

Don’t waste your time

If you have understood everything from the introduction and know what a double dispatch or the visitor pattern is, then it is not necessary to finish reading the chapter. In this case, proceed with https://ekvip.atlassian.net/wiki/x/bQAbYg

Explanation based on an example

Here is an example:

  • If a pneumatic cylinder is replaced by a servo axis because the axis is faster and still has more smoothness, the function remains the same, namely: move to home position and move to work position. It is effortless to develop a container-class for a servo axis with two teach positions that implements the interface ICylinder.

Sometimes it's more likely that the data stays the same but new functions are added. Here is a lame example:

  • For rectangular tabs of a battery cell, a vision system gives the positions of angles A B C and D. This data is stored in a structure. It is used to calculate the center point with the CalculateCenter function to weld the busbar on it. Now is there a new requirement that the machine should store the area of the tab for the product data. Here it is now very easy to add a new function CalculateArea.

There are now two consequences:

  • Developers should be careful with getters, setters, and properties of normal classes, because they can create dependencies between the client class and the inner data structure of an object.

  • For pure data transfer objects (DTOs) we use objects with a method to dispatch functionality and objects with the functionality, this technique is called double dispatch (two are objects necessary to dispatch it).

Double Dispatch

Double dispatch is a technique to extract methods to classes, it’s useful if to extend the functionality later. I will show how it works on data objects, to keep it simple I’ll use two shapes a rectangle and a square.

  • The rectangle has the two side lengths a and b as LREAL

  • The square has only one side length a as LREAL

  • The functionality will be calculate area and convert shape to json object.

Of course, we first define the interfaces IShape, IShapeVisitor, IRectangle and ISquare. Then we implement the classes Rectangle, Square, ShapeArea and JsonShape.

Interfaces

Interface IShape
INTERFACE IShape EXTENDS __SYSTEM.IQueryInterface
METHOD accept
VAR_INPUT
	visitor	:IShapeVisitor;
END_VAR
Interface IShapeVisitor
INTERFACE IShapeVisitor EXTENDS __SYSTEM.IQueryInterface

METHOD visitRectangle
VAR_INPUT
	rectangle	:IRectangle;
END_VAR
METHOD visitSquare
VAR_INPUT
	square	:ISquare;
END_VAR
Interface IRectangle
INTERFACE IRectangle EXTENDS IShape

PROPERTY a :LREAL
Get
Set
PROPERTY b :LREAL
Get
Set
ISquare
INTERFACE ISquare EXTENDS IShape
PROPERTY a :LREAL
Get
Set

Implementation

Class Rectangle
FUNCTION_BLOCK Rectangle IMPLEMENTS IRectangle
VAR
	sideLenghtA		:LREAL;
	sideLenghtB		:LREAL;
END_VAR
GET PROPERTY a :LREAL


a := THIS^.sideLenghtA;
SET PROPERTY a :LREAL


THIS^.sideLenghtA := a;
GET PROPERTY b :LREAL


b := THIS^.sideLenghtB;
SET PROPERTY b :LREAL


THIS^.sideLenghtB := B;
METHOD accept
VAR_INPUT
	visitor	: IShapevisitor;
END_VAR
visitor.visitRectangle(THIS^);
Class Square
FUNCTION_BLOCK Square IMPLEMENTS ISquare
VAR
	sideLength		:LREAL;
END_VAR

GET PROPERTY a :LREAL


a := THIS^.sideLenght;
SET PROPERTY a :LREAL


THIS^.sideLenght := a;
METHOD accept
VAR_INPUT
	visitor	: IShapevisitor;
END_VAR
visitor.visitSquare(THIS^);
Class ShapeArea
FUNCTION_BLOCK ShapeArea IMPLEMENTS IShapeVisitor
VAR
	shapeArea		:LREAL;
END_VAR
GET PROPERTY area :LREAL


area	:= THIS^.shapeArea;
METHOD visitRectangle
VAR_INPUT
	rectangle	:IRectangle;
END_VAR

THIS^.shapeArea := (rectangle.a * rectangle.b);
METHOD visitSquare
VAR_INPUT
	square	:ISquare;
END_VAR
THIS^.shapeArea := (square.a * square.a);
Class JsonShape
FUNCTION_BLOCK JsonShape IMPLEMENTS IShapeVisitor
VAR
	jsonShape	:Tc2_System.T_MaxString;
END_VAR
GET PROPERTY shape :Tc2_System.T_MaxString


shape := THIS^.jsonShape;
METHOD visitRectangle
VAR_INPUT
	rectangle	:IRectangle;
END_VAR

THIS^.jsonShape := Tc2_Standard.CONCAT(
	STR1 := '{"rectangle":{"a":',
	STR2 := Tc2_Standard.CONCAT(
		STR1 := Tc2_Standard.TO_STRING(rectangle.a),
		STR2 := Tc2_Standard.CONCAT(
			STR1 := ', "b":',
			STR2 := Tc2_Standard.CONCAT(
				STR1 := TO_STRING(rectangle.b),
				STR2 := '}}'
			)
		)
	)
);

METHOD visitSquare
VAR_INPUT
	square	: ISquare;
END_VAR
THIS^.jsonShape := Tc2_Standard.CONCAT(
	STR1	:= '{"square":{"a":',
	STR2	:= Tc2_Standard.CONCAT(
		STR1 := TO_STRING(square.a),
		STR2 := '}}'
	)
);
	

Summary

The example shows well how objects can later be extended by functions. For example, it is not difficult to create a xml shape here. However, this flexibility comes at a price because if there is new data, such as the triangle shape, then the visitor interface IShapeVisitor and therefore all visitors must be adapted.

We use data objects instead of data structures because these are more flexible, especially the access control, polymorphism for collections. Normally, it is not common that functionality changes because a cylinder is cylinder it can extend and retract. It is ok to use type structs inside the data object, sometimes it is necessary to use data structures inside data objects, e.g. if the TwinCAT xml server is used as a persistence service.