ООП в графических языках программирования. ч.2 МОП и ООП

    В первой части я попытался показать, что черная кошка ООП в темной комнате графических языков существует, даже если это не кошка, а полудохлый кот живодера Шредингера, который то есть, то его нет. Был показан пример реализации методологии объектно-ориентированного программирования, когда программа – это не код на языке С++ или Java, а диаграмма Simulink, SimInTech, SimulationX или SCADE Esterel, — любая графическая нотация описания алгоритма.


    В рекламных материалах Мatlab Simulink часто используют термин МОП — Модельно Ориентированное Проектирование (Model-based design). Во многих текстах они подчёркивают, что графическая схема алгоритма это модель, что, конечно, верно. Однако в первоначальном определении МОП, модель – это прежде всего модель объекта, к которому разрабатывают систему управления, включая управляющее программное обеспечение. Подробнее методолгия МОП описана здесь. Таким образом, разрабатывая систему управления по методологии МОП можно и нужно использовать методологию ООП для разработки управляющего ПО. И что бы окончательно закрыть вопрос с моделями, вот вам картинка с отличиями одного от другого. Если все понятно, то можно дальше не читать.




    Вернемся к графическим языкам и ООП применительно к управляющим программам для промышленности. Графический язык программирования обеспечивает запись программы в виде схемы из набора блоков, соединенных линиями связи. Как правило, в промышленном программировании из графической схемы автоматическим кодогенератором создается код для компилятора и последующей загрузки в целевую аппаратуру.


    В рамках моей специальности мне приходилось работать с управляющим ПО для АЭС, где стандартами безопасности запрещено использовать С++, только чистый Си, а в некоторых случаях даже не Си, а «железную» логику, где алгоритмы реализованы в виде электронной схемы на транзисторах и реле. И за соблюдением стандартов следят суровые парни из надзорных органов.



    Но и тут, как бы удивительно это не звучало, разработка все равно идет с использованием методологии ООП. Потому как, когда ООП использовать нельзя, но очень хочется, то можно. Правда, потом нужно все вернуть назад, и что бы никакого С++ и итоговом коде не оказалось, ибо безопасность и нормативы «über alles». Как говорится, и рыбку съесть, и за нарушения не сесть.


    Чтобы сделать настоящий объект по определению ООП, мы связываем структуры данных и схемы их обработки в единый объект, это называется инкапсуляция. А поскольку для надежных систем АЭС нам нельзя использовать С++, мы при генерации кода должны все это разобрать обратно. Как пояснили в комментариях к предыдущей статье, первая компилятор С++ СFront работал так же, осуществлял трансляцию ООП С++ кода в чистый Си.


    В первом варианте реализации ООП в графическом языке у нас был создан специальный блок, содержащий графическую расчетную схему. Этот блок при инициализации привязывал схему (метод) класса к конкретному «экземпляру класса» — набору переменных, поименованных специальным способом.


    Рассмотрим второй вариант реализации методологии ООП в графических языках программирования. На рисунке 1 изображена схема работы алгоритма обработки датчика.



    Рисунок 1. Программа обработки показаний датчика.


    Это метод класса. Ему соответствует в базе данных своя категория «Датчики» — абстрактный класс с заданным наборов полей и экземпляр класса KBA31CFO1 конкретный датчик. У данного датчика поля имеют конкретные значения, часть полей задаются пользователем, часть полей рассчитывается процессе выполенения программы. см. рис. 2



    Рисунок 2. База данных сигналов с открытой категорией «Датчик».


    Пока все как в первом варианте, где у нас формировалась привязка расчетной схемы к конкретному датчику при установке блока на схему. «А где разница?» — спросите вы. А разница – внутри блока. Если в первом варианте внутри была расчетная схема, которая копировалась при каждой установке блока, то в этом варианте внутренность выглядит примерно так:



    Рисунок 3. Внутренности схемы блока экземпляра класса.


    Вместо расчетной схемы внутри блока «изображены» только передача и прием данных.
    А само выполнение расчета происходит в другом месте, на схеме с рисунка 1. В некоторых случаях можно вообще не использовать блоки на расчетной схеме, достаточной наличия экземпляров класса датчика в базе данных сингналов. Это и есть второй способ реализации инкапсуляции в графических языках. Фишка в том, что все блоки на схеме рисунка 1 векторные, и они обрабатывают не один сигнал, а вектор сигналов со всех датчиков данного типа в расчетной схеме. Если включить режим отображения результатов на линии связи, то мы увидим, что каждая линия связи содержит не одну цифру, а вектор из 4 чисел (по числу датчиков в базе данных).



    Рисунок 4. Схема обработки сигнала датчиков в режиме просмотра значений.


    Таким образом, одна схема обработки реализует обработку всех датчиков в проекте, причем каждый датчик обрабатывается со своими параметрами, заданными в базе данных как характеристики конкретного экземпляра класса. Например, ограничитель берет из базы данных максимальное значение, которое задано одинаковым для первых трех датчиков и отличается у четвёртого. (см. рис. 5)



    Рисунок 5. Параметры блока ограничителя в схеме расчета.


    А что там с результирующим кодом, который автоматически сгенерируется по этой чудесной схеме, как удается избежать артефактов ООП? Все просто: никакого обмана и никакого ООП, в коде чистый Си. Для каждого блока схемы векторной обработки будет сформирован цикл, обеспечивающий столько вычислений, сколько экземпляров класса есть в проекте. В нашем случае датчиков 4, поэтому мы сначала формируем массивы размерностью «4», путем чтения сигналов из датчиков:


    /* 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
    

    Потом сортируем все блоки по порядку и запускаем их в цикле. Для каждого элемента массива типа каждый блок вычислений выполнится для всех датчиков.


    /* 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];
     …
    }
    

    После расчета записываем сигналы для каждого экземляра класса:

    /* 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];
    

    Как видим, в конечном коде никаких объектов нет. Чистый, невинный и безопасный СИ. В приведенном примере реализации ООП в графическом языке векторная схема обсчитывает все однотипные датчики. Такой прием позволяет изменить одну схему для изменения обработки всех датчиков.


    Другим дополнительным преимуществом такого подхода является страховка от ошибок. Представьте себе: вы вручную добавляете датчик и в одном месте забыли увеличить в цикле количество повторений при обработке. Никаким статическим анализатором кода эту ошибку обнаружить будет невозможно, код корректный. И даже на работе это может не сказаться сразу и очевидным образом.


    Ну, и в конце, обещанный полиморфизм и наследование. В первом методе пользователь получал множество одинаковых схем, которые он мог править уже после установки блока субмодели и, тем самым, осуществлять полиморфизм, изменяя поведение конкретного экземпляра класса. Я думаю, все догадались, что можно для конкретного датчика изменить схему обработки, и мы получим новый класс, у которого совпадают поля, но методы отличаются. Так же можно добавить новые поля и получить новый класс с другим полями, содержавшими все поля родителя и методы родителя.


    На рисунке 6 — пример двух блоков классов «родитель» и «наследник». Внутри блока сохраняется схема расчета родительского класса. Все данные уходят в общий векторный блок, аналогичный блоку на рис. 4. Полностью повторяется метод родительского класса. А потом у класса-наследника появляются дополнительное поле Yатм и дополнительный пересчет величины с помощью блока линейной интерполяции.


    Таким образом, сохраняется возможность менять методы обработки родителя, которые поменяются во всех классах-наследниках, а так же индивидуально настраивать поведение наследника.



    Рисунок 6. Полиморфизм у наследников классов.


    Подводя итог, можем утверждать, что методология ООП может быть использована при создании ПО в графических языках программирования. Абстрагирование, инкапсуляция, наследование, полиморфизм – все эти принципы легко и непринужденно реализуются правильными средствами разработки. Важно отметить, что итоговый код, после автоматической генерации из графического языка, остается чистым безопасным Си без всякого ООП


    В некторых случаях результатом разработки управляющего ПО, в графическом виде явялется не код Си, для загрузки в контроллеры, а принципиальная электрическая схема «железная логика», но при этом методика изложенавя выше ООП так же работает прекрасно работает.

    • +3
    • 10,9k
    • 8
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 8

      +1
      диаграмма Simulink, SimInTech, SimulationX или SCADE Estere

      AnyLogic в копилочку :)
        0
        AnyLogic тоже генирирует код Си из расчетных схем?
          0
          Нет, Java. И его можно кастомизировать прямо из рабочей среды.
            0
            Тогда разработчика AnyLogic легче, и не нужно все в безопасный Си разбирать обратно.
        +2
        Даже при переносе с Simulink в C или С++ встроенными методами идут ошибки в коде в некоторых алгоритмах, то писателю внешнего обработчика можно дать медаль)
          0
          Ошибки там в принципе не должны быть это же математика. 2+2 равно 4, а не как не 5
            +1
            это же математика. 2+2 равно 4, а не как не 5
            А uint(2) + int(-2) сколько будет?
              0
              неужели не 0?

        Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

        Самое читаемое