Codesys. Объектно-ориентированное программирование на ПЛК. Часть 2. Классы и интерфейсы, методы и свойства.

Продолжаем наше вхождение в тему объектно-ориентированного программирования на ПЛК для среды Codesys.

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

Наша задача в этот раз разобраться с двумя основными объектами, которые мы будет использовать — Класс и Интерфейс, а также понять два инструмента — методы и свойства.

Все эти понятия будут раскрыты с учетом особенностей реализации и только для платформы Codesys.

Содержание

Методы

Методы являются расширением стандарта IEC 61131-3 и инструментом объектно-ориентированного программирования, который используется для инкапсуляции данных. Метод содержит объявление и реализацию, которая включает серию операторов. Однако, в отличие от функции, метод не является независимым программным модулем и назначается функциональному блоку или программе.

Из это следует что Метод — это функция, которая привязана к определенному функциональному блоку и имеет модификаторы доступа. Из того, что метод является частью функционального блока(класса), то он может оперировать всеми внутренними данными функционального блока.

А так метод — это функция, то и данные в себе она не хранит, они инициализируются каждый раз при вызове и после вызовы удаляются.

Также методы имеют свои модификаторы доступа

МодификаторОписание
InternalМетод может вызываться только в своем собственном пространстве имен.
ProtectedМетод может вызываться только в своем собственном программном модуле и его производных.
PrivateМетод может вызываться только в своем собственном программном модуле.
Public Метод можно вызывать из любого места.

Свойства

Свойства — это расширение стандарта IEC 61131-3 и инструмент для объектно-ориентированного программирования. Свойства используются для инкапсуляции данных, поскольку они обеспечивают внешний доступ к данным и одновременно действуют как фильтры. Для этой цели свойство предоставляет методы доступа Get и Set, которые обеспечивают доступ для чтения и записи к данным экземпляра под свойством.

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

Является по своей сути функцией, которая переписывает операцию присваивания в зависимости от расположения.

Свойства также как и методы имеют модификаторы доступа.

МодификаторОписание
Internal Доступ ограничен пространством имен (библиотекой).
ProtectedДоступ ограничен программой, функциональным блоком или GVL с его производными.
PrivateДоступ ограничен программой, функциональным блоком или GVL
PublicДоступ не ограничен

Интерфейсы в Codesys

Теперь переходим к первой части нашего конструктора.

Интерфейс — это средство объектно-ориентированного программирования. Объект ITF описывает набор прототипов методов и свойств. В этом контексте прототип означает, что методы и свойства содержат только объявления и не содержат реализации.

Первая часть, которую надо понять. Интерфейс играет описательную роль, а не функциональную. В интерфейсе нет реализации никакой логики. В нем мы указываем набор методов и свойств, логика которых ОБЯЗАТЕЛЬНО должна быть реализована в классах, которые будут принадлежать этому интерфейсу.

Возникает весьма закономерный вопрос, а зачем нам нужен этот объект, который не работает, в обычном для нас понимании?

И вот тут работает вторая часть. Интерфейс — это контракт. Наследуясь от интерфейса программист обязуется реализовать методы и свойства, которые указаны в этом интерфейсе, другая сторона контракта гласит, что твой класс может быть использован в любом месте где указан данный интерфейс и все отработает правильно.

Классы в Codesys

Обратимся к определению Codesys

Класс — это логическая единица с инкапсулированными данными и операциями. Он также представляет собой тип переменной, который может быть создан. Когда экземпляр класса назначается в качестве переменной другому классу, это называется ролью или выражением класса. У класса может быть метод, FB_Init который вызывается при инициализации экземпляра.

Начнем с того, что данная логическая единица может быть представлена функциональным блоком или программой.

Данный функциональный блок или программа могут либо реализовывать какой-либо интерфейс, либо расширять какой либо функциональный блок, либо самостоятельно все реализовывать.

Пока мы будем рассматривать только эти три состояния. Без композиций и ассоциаций.

Функциональный блок как Класс включает в себя всю логику и данные, которые необходимы для работы этого класса.

Процедурное и объектное(Практика)

Теперь настало время практики. Давайте разберем немного процедурный подход и объектный. И тут стоит обозначить несколько вещей. Во-первых, я пишу код так как пишу. Мой подход может отличаться от вашего, из-за этого мы приветствуем конструктивную критику. Во-вторых, если вы пишите код, используя классы, то это еще не ООП. В-третьих, ооп — это не про код. ООП — это, в большинстве своем, про данные, их организацию и взаимодействия.

Процедурное

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

Оператор должен понимать: работает от или нет, управление может быть ручное или автоматическое, есть ли внешняя блокировка, включен ли сервисный режим, ну и например включен ли автомат.

Пункт номер один — готовим структуру данного насоса.

структура данных udtPump

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

Но если мы немного усложним структуру

искусственно усложненная структура

То теперь мы видим, что у нас есть ErrorValue которое является интом от 40 до 150, мы конечно можем включить неявную проверку, а можем сами проверять.

Дальше пойдем к созданию FB который бы работал с данными где и будет прописан логика.

Вот так будет выглядеть организация данных в ФБ. Почему ссылка в секции VAR_Input, а не просто udtPump в VAR_IN_OUT? Стоит понимать, что значение в VAR_IN_OUT — это тоже ссылка, так что разницы никакой, мне просто так удобнее. А если вам интересно почему я не обозначил udtPump в секции VAR, то ответ прост — много писать для работы с визуализацией. Грубо говоря, я пишу прям в модель из представления, что делать нельзя, но об этом в другой раз.

Логику взаимодействия мы пропускаем. так как нас это не интересует пока что.

Объектное

Сейчас задачей будет преобразовать FB из процедурного стиля в объектный, т.е. класс должен включать в себя все необходимые данные и методы для работы.

И начнем мы все это с Интерфейса. Можно и без него, но потом он иногда нужен, так что давайте с ним.

Для создания интерфейса ПКМ->Добавить объект->Интерфейс. Там даем имя интерфйесу

Добавление методов и свойств происходят через контекстное меню интерфейса «Добавление объекта»

Начнем с методов. Что мы можем сделать с насосом? Включить/выключить в ручном режиме, включить выключить в автоматическом, включить ручной режим, включить автоматический, включить сервисный режим.

Методы itfPump

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

Свойства нам говорят, что они возвращают. Это состояния насоса. Допустим свойство isRun должно нам вернуть состояние насоса в работе ли он.

Причем как видно у нас есть только метод Get, чтобы мы не могли ничего записать из вне.

А теперь давайте мы создадим функциональный блок, который будет все это реализовывать.

окно создания функционального блока

И после того как мы добавим данный функциональный блок мы получаем класс с кучей методов, которые нам надо реализовать.

Теперь загоняем нашу структуру в секция VAR. можно целиком структуру, можно все скопировать, я скопировал и осталось решить проблему динамически изменяемых переменных.

Для наглядности они прям одинаковые пока что. Так как мы только начинаем свои шаги в ООП, что мы можем сделать. Если красивые варианты, есть красивые и сложные, а есть «в лоб» и пока мы голосуем за третий вариант.

Для третьего варианта нам надо будет в классе создать метод Update, который мы будем вызывать циклически. И осталось определиться как нам получать входные данные. Есть вариант при инициализации функционального блока, есть вариант при инициализации системы, если вы пишите инициализацию, ну а есть вариант просто в методе, для простоты возьмем последний.

Метод создается также через контекстное меню функционального блока.

И вот что мы получим в конечном итоге

Возникает вопрос, а что это за конструкция THIS^. THIS^ — это разыменованый указатель на конкретный экземпляр класса. Это доступ к полям класса, а не к полям метода. Как можно заметить называются они одинаково.

Далее прописываем всю логику обновления, какая у нас будет.

Ну и к примеру забираем значение свойства isRun()

Тут все просто мы нашему свойству передаем значение нашей переменной внутри функционального блока. в случае присваивания логика обратная.

И теперь создавая какой-то другой насос мы будем его наследовать от этого интерфейса и реализовывать методы для нового насоса(и вот тут было бы неплохо внести метод Update в интерфейс)

Но в чем же сила интерфейсов? А в том, что классы наследуемые от одного интерфейса заменяемы. Для этого мы создадим еще один функциональный блок.

Во входных данных мы указываем, что на входе у нас будет объект унаследованный от интерфейса itfPump, а логика внизу оперирует методами и свойствами интерфейса, которые обязательно должны быть реализованы в функционально блоке, который принадлежит данному интерфейсу. И теперь я могу даже в рантайме менять насосы для этого функционального блока.

Итоги

В статье были затронуты основные инструменты для ООП. И на основе этих инструментов у нас и будет развиваться вся логика. Дальше у нас будут более абстрактные темы и более концептуальные.

С вопросами — info@engcore.ru

Новости из мира автоматизации и мое скромное мнение- https://t.me/wtfcontrolsengineer

Ответить