OOP in graphical programming languages. Part 2 MOS and OOP
In the first part, I tried to show that a black OOP cat in a dark room of graphic languages exists, even if it is not a cat, but a half-dead cat of the Schroedinger flayer, which is, that is, it is not. An example of the implementation of the methodology of object-oriented programming was shown, when a program is not C ++ or Java code, but a Simulink, SimInTech, SimulationX or SCADE Esterel diagram — any graphical notation of the algorithm description.
In advertising materials Matlab Simulink often use the term MOS - Model-based Design (Model-based design). In many texts, they emphasize that the graphical diagram of the algorithm is a model, which, of course, is true. However, in the initial definition of MOS, the model is primarily the model of the object to which the control system is developed, including control software. More MOS methodology is described here. Thus, when developing a control system according to the MOS methodology, it is possible and necessary to use the OOP methodology for the development of management software. And to completely close the question with models, here's a picture with the differences of one from the other. If everything is clear, then you can no longer read.
Let us return to graphical languages and OOP as applied to control programs for industry. The graphical programming language provides a program record in the form of a diagram from a set of blocks connected by communication lines. As a rule, in industrial programming, a code for the compiler and subsequent loading into the target equipment is created from a graphic circuit by an automatic code generator.
As part of my specialty, I had to work with managing software for nuclear power plants where it is forbidden to use C ++ with safety standards, only pure C, and in some cases not even C, but “iron” logic, where the algorithms are implemented as electronic circuits on transistors and relay. And the observance of standards is monitored by harsh oversight guys.
But here, no matter how surprising it may sound, development is still going on using the OOP methodology. Because when you can’t use OOP, but you really want to, you can. True, then everything needs to be returned back, and that would not be any C ++ and the final code, for safety and über alles standards. As they say, to eat a fish, and not to sit down for violations.
To make a real object by the definition of OOP, we bind data structures and processing schemes into a single object, this is called encapsulation. And since we cannot use C ++ for reliable NPP systems, we must parse all this when generating the code. As explained in the comments to the previous article, the first C ++ СFront compiler worked in the same way, it translated OOP C ++ code into pure C.
In the first version of the implementation of OOP in a graphical language, we created a special block containing a graphical calculation scheme. During initialization, this block bound the class scheme (method) to a specific “class instance” - a set of variables named in a special way.
Consider the second embodiment of the OOP methodology in graphical programming languages. Figure 1 shows the operation of the sensor processing algorithm.
Figure 1. Sensor processing program.
This is a class method. It corresponds to its own category “Sensors” in the database - an abstract class with a given set of fields and an instance of the KBA31CFO1 class specific sensor. For this sensor, the fields have specific values, some of the fields are set by the user, some of the fields are calculated during the execution of the program. see pic 2
Figure 2. Database of signals with the open category “Sensor”.
So far, everything is as in the first embodiment, where we formed the binding of the design scheme to a specific sensor when installing the unit on the circuit. “Where is the difference?” You ask. And the difference is inside the block. If in the first version there was a calculation scheme inside, which was copied with each installation of the block, then in this version the inside looks something like this:
Figure 3. The interiors of a block instance of a class instance.
Instead of the design scheme, only data transmission and reception are “shown” inside the block.
And the calculation itself takes place elsewhere, in the diagram from Figure 1. In some cases, it is possible not to use the blocks at all in the calculation diagram, it is sufficient to have instances of the sensor class in the singles database. This is the second way to implement encapsulation in graphic languages. The trick is that all the blocks in the diagram of Figure 1 are vectorial, and they process not one signal, but a vector of signals from all sensors of this type in the calculation scheme. If you enable the display mode of the results on the communication line, then we will see that each communication line contains not one digit, but a vector of 4 numbers (according to the number of sensors in the database).
Figure 4. Diagram of signal processing of sensors in the value viewing mode.
Thus, one processing scheme implements the processing of all sensors in the project, with each sensor being processed with its own parameters specified in the database as the characteristics of a particular instance of the class. For example, the limiter takes the maximum value from the database, which is set the same for the first three sensors and differs for the fourth. (see fig. 5)
Figure 5. Parameters of the limiter block in the calculation scheme.
And what about the resulting code, which is automatically generated according to this wonderful scheme, how do you manage to avoid OOP artifacts? It's simple: no cheating and no OOP; pure C in the code. For each block of the vector processing scheme, a cycle will be formed that provides as many calculations as there are class instances in the project. In our case, there are 4 sensors, so we first form arrays of dimension “4” by reading the signals from the sensors:
/* Index=104
UID=104
GeneratorClassName=TSignalReader
Name=buz1.sensor.Macro6.Macro3.Macro157.SignalReader3
Type=Чтение из списка сигналов */
};
state_vars->kbastdv104_out_0_[0] = kba31cf001_mf_type;
state_vars->kbastdv104_out_0_[1] = kba32cf001_mf_type;
state_vars->kbastdv104_out_0_[2] = kba33cf001_mf_type;
state_vars->kbastdv104_out_0_[3] = uf40y329084320_mf_type
Then we sort all the blocks in order and run them in a loop. For each element of an array of type, each block of calculations will be performed for all sensors.
/* Index=211
UID=211
GeneratorClassName=TAndSrc
Name=buz1.sensor.Macro6.And61
Type=Оператор И */
for(i=0;i<4;i++){
locals->v211_out_0_[i] = state_vars->kbastdv125_out_0_[i] && (!(locals->v191_out_7_[i] > 0.5));
/* Index=212
UID=212
GeneratorClassName=TMulDbl
Name=buz1.sensor.Macro6.Mul_oper1
Type=Перемножитель */
locals->v209_out_2_[i] = consts->kbastdv121_a_[i]*state_vars->kbastdv127_out_0_[i];
/* Index=213
UID=213
GeneratorClassName=TSumSrc
Name=buz1.sensor.Macro6.Add_oper1
Type=Сумматор */
locals->v209_out_3_[i] = (1)*consts->kbastdv122_a_[i]+(1)*locals->v209_out_2_[i];
…
}
After the calculation, we record the signals for each instance of the class:
/* Index=776
UID=776
GeneratorClassName=TSignalWriter
Name=buz1.sensor.Macro6.Macro3.SignalWriter4
Type=Запись в список сигналов */
kba31cf001_mf_xb01 = state_vars->kbastdv207_out_0_[0];
kba32cf001_mf_xb01 = state_vars->kbastdv207_out_0_[1];
kba33cf001_mf_xb01 = state_vars->kbastdv207_out_0_[2];
uf40y329084320_mf_xb01 = state_vars->kbastdv207_out_0_[3];
As you can see, there are no objects in the final code. Clean, innocent and safe SI. In the above example, the implementation of OOP in a graphic language, a vector circuit calculates all the sensors of the same type. This technique allows you to change one scheme to change the processing of all sensors.
Another added benefit of this approach is error insurance. Imagine: you manually add a sensor and in one place you forgot to increase the number of repetitions during processing in a cycle. No static code analyzer can detect this error, the code is correct. And even at work this may not affect immediately and in an obvious way.
Well, in the end, the promised polymorphism and inheritance. In the first method, the user received many identical schemes, which he could edit after installing the submodel block and, thereby, carry out polymorphism, changing the behavior of a particular instance of the class. I think everyone guessed that it is possible to change the processing scheme for a particular sensor, and we will get a new class that has the same fields, but the methods are different. You can also add new fields and get a new class with different fields containing all the fields of the parent and the methods of the parent.
Figure 6 shows an example of two blocks of the classes “parent” and “heir”. Inside the block, the parent class calculation scheme is saved. All data goes into a common vector block, similar to the block in Fig. 4. The parent class method is completely repeated. And then the successor class has an additional field Yatm and an additional conversion of the value using the linear interpolation block.
Thus, it remains possible to change the processing methods of the parent, which will change in all classes-heirs, as well as individually customize the behavior of the heir.
Figure 6. Polymorphism in class heirs.
To summarize, we can argue that the OOP methodology can be used to create software in graphical programming languages. Abstraction, encapsulation, inheritance, polymorphism - all these principles are easily and naturally implemented by the right development tools. It is important to note that the resulting code, after automatic generation from a graphic language, remains pure safe C without any OOP
In some cases, the result of the development of the control software in graphical form is not the C code for loading into the controllers, but the circuit diagram “iron logic”, but the technique described above OOP also works just fine.