Определение объекта

 

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

Язык Modelica его авторы называют языком «физического» моделирования. На первый план выдвигаются проблемы компонентного моделирования с неориентированными блоками, а средства описания дискретных аспектов поведения и алгоритмических действий отступают на второй план и подчиняются исключительно требованиям автоматического анализа совокупной системы уравнений. Компонентное моделирование с неориентированными блоками является важной, но все же ограниченной областью моделирования при проектировании современных технических систем.

В сложной технической системе на верхних и средних уровнях иерархии предположение о направленности связей обычно является вполне разумным. Неслучайно, что такие традиционные инструменты, как Simulink или VisSim, продолжают широко использоваться, несмотря на свою «отсталость» в области ООМ.

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

Выбору в качестве основы языка моделирования алгоритмического языка, способствовало то, что первые языки программирования (Fortran, Algol) являлись действительно алгоритмическими языками. Впоследствии в алгоритмические языки практически всегда включались низкоуровневые конструкции, полезные для повышения эффективности кода, но чрезвычайно затрудняющие понимание программ (пример - язык С).

Развитие программирования совершило очередной виток и появился новый объектно-ориентированный язык программирования Java, который является также алгоритмическим и не содержит никаких машинно-ориентированных конструкций. Однако Java и очень близкий к нему язык С# являются реальными кандидатами на роль базового языка для OOML.

Использование Java удобно с практической точки зрения. Современный инструмент моделирования, входной язык которого допускает определение классов пользователем, должен быть компилирующим. Практически всегда в компилирующих пакетах моделирования используется некоторый промежуточный язык программирования (Fortran, С, Pascal). При этом либо пользователь вынужден устанавливать у себя на компьютере конкретную версию компилятора этого языка, либо эта версия должна устанавливаться вместе с пакетом моделирования.

Появляется ряд проблем. Java как промежуточный язык программирования является почти идеальным вариантом: минимальный набор инструментальных средств доступен всем и распространяется бесплатно, имеются версии ддя всех практически используемых платформ, достаточно жестко отслеживается совместимость версий «снизу-вверх», упрощается присоединение программного кода пользователя.

Непосредственное встраивание Java в язык моделирования (любая переменная может быть объектом Java) имеет и ряд минусов:

- необходимость откладывать до стадии компиляции контроль синтаксиса алгоритмических операторов;

- отсутствие апробированных библиотек численных методов;

- несколько меньшая скорость выполнения модели по сравнению с «родным» для данной платформы кодом.

В настоящее время единственным инструментом моделирования гибридных систем с непосредственным встраиванием языка Java в качестве элемента языка моделирования является пакет AnyLogic.

Более разумным представляется компромиссный вариант, когда пакет моделирования непосредственно поддерживает небольшое подмножество базового алгоритмического языка, позволяя одновременно использовать внешние алгоритмические объекты, написанные на базовом языке или других объектно-ориентированных языках программирования и откомпилированные соответствующим компилятором. Для этого компилятору пакета моделирования должны быть доступны сведения о внешнем интерфейсе объектов. В случае использования Java это вполне возможно, если сам компилятор языка моделирования написан на Java. Особенно привлекательной для реализации такого подхода является среда MS NET Runtime, т.к.:

- с помощью механизма рефлексии (reflection) доступна информация о внешнем интерфейсе объекта, созданного с помощью любого языка, поддерживающего соглашения этой среды;

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

Рассмотрим понятие объекта в ООМ.

Объектом принято называть некоторую сущность, которая инкапсулирует в себе данные и методы как единое целое и взаимодействует с внешним окружением через определенный интерфейс. С понятием объекта тесно связано отношение двойственности «класс-экземпляр». Каждый объект всегда является экземпляром какого-то класса (однако не каждый экземпляр класса - объект).

Кандидатом на роль объекта в компонентном моделировании является блок. Блок ‑ это совокупность переменных и поведения, он взаимодействует с внешним миром только через внешние переменные. Блок ‑ всегда экземпляр некоторого класса. Например, когда в пакете Simulink, который формально не поддерживает ООМ, размещаете на функциональной схеме новый усилитель, то неявно порождаете новый экземпляр предопределенного класса Gain из библиотеки Linear blocks. Два усилителя, использованные в схеме, имеют один и тот же набор переменных Х,Y,К, один и тот же интерфейс взаимодействия с внешним миром - вход Х и выход Y, один и тот же закон функционирования - описываемый формулой Y=КХ, но могут иметь различные значения переменных. В Simulink по умолчанию новые экземпляры анонимны (какой-то экземпляр класса Gain), но можно дать этим двум усилителям свои имена (например, G1 и G2).

С точки зрения UML, объект ‑ это совокупность данных (атрибутов), методов и «машины состояний» в случае активного объекта (см. далее), а «блок» («block») - это некоторый стереотип, указывающий на семантические ограничения для данной категории классов. Соответственно «параметр» («parameter»), «вход» («inputi») и «выход» («output») являются стереотипами, уточняющими семантику для этих групп переменных. Наличие внутренней структуры в блоке-контейнере с позиций ООМ означает наличие внутренних переменных типа «блок» и дополнительных функций, отражающих уравнения связей. К сожалению, точно уложиться в такую трактовку объекта для непрерывных блоков, как будет показано ниже, не всегда удается.

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

Рассмотрим источник синусоидального сигнала, включающий в себя генератор и усилитель (см. рис. 10.1).

 

 

Рис. 10.1

 

Генератор непрерывно изменяет значение выхода у согласно формуле y=sin(2pwt+j0). Усилитель непрерывно преобразует значение входа в значение выхода согласно формуле Y=КХ. Блок-контейнер непрерывно передает сигнал по связям согласно формулам Amp.X=Gen.y и Y=Amp.Y. Такие активные объекты, осуществляющие непрерывное отображение, называют активными динамическими объектами.

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

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

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

С позиций UML чисто непрерывный блок должен иметь вырожденную карту состояний, состоящую лишь из одного узла, которому приписана непрерывная деятельность, заданная некоторой системой уравнений. На рис 10.2 показана карта состояний ддя генератора синусоиды, где под SuneF подразумевается формула y=sin(2pwt+j0).

 

Рис. 10.2

 

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

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

Опишем изображенную на рис. 10.1 систему с помощью OOML. Целью является иллюстрация основных идей ООМ, а не описание какого-то конкретного языка, поэтому в примерах используется Java-подобный синтаксис, не претендующий на полноту и строгость:

block class CGain {

parameter Real K = 1;

input Real X = 0;

output Real Y = 0;

eguation classmain {

Е1: Y = КХ;

};

behavior {

do main;

 

};

}/*СGain*/

block class CSineGenerator {

parameter Real Period = 2;

output Real Y;

eguation classmain {

Real Omega = 2*Pi/Period;

Y = sin(Omega*time+InitialPhase;

};

behavior { main; };

}/*CsineGenerator*/

block class CsineSource {

parameter Real Amplitude = 1;

parameter Real Frequency = 1;

output Real Y;

// structure

CsineGenerator Gen = new CsineGenerator Gen (Period=1/Frequency);

CGain Amp = new CGain (K=Amplitude);

behavior(

do eguation class {

connect(Gen.Y,Amp.Y,X);

connect(Amp.Y,Amp.Y,X);

};

};

}/*CsineSource*/.

Обратите внимание, что формула в непрерывном поведении блока Cgain помечена специальной меткой E1. Это необходимо для ее переопределения в классе-потомке.

Экземпляры блока могуг быть статическими и динамическими. Статический экземпляр блока создается автоматически при создании экземпляра, охватывающего блока. Например, блоки Gen и Amp возникнут автоматически при создании экземпляра блока SineSource (см. рис. 10.1). Экземпляры блоков самого верхнего уровня вложенности создаются при запуске модели. Таким образом, если все блоки модели статические, то время их существования совпадает со временем прогона модели. Время существования динамических экземпляров блоков в общем случае меньше времени прогона модели. Заметим, что если динамический блок имеет статическую структуру, то она создается и уничтожается вместе с этим блоком.

Например, при динамическом создании блока SineSource (рис. 10.1) блоки Gen и Amp будут созданы автоматически и автоматически уничтожены при динамическом уничтожении блока. В случае поддержки пакетом моделирования динамических блоков необходимо также поддерживать и динамическое создание и уничтожение связей между блоками.

В языках моделирования принят способ обращения к конструктору объекта несколько иной, чем в большинстве языков программирования. Некоторая группа внешних переменных вьделяется как параметры. Значения параметров могут изменяться только при создании экземпляра блока, во время прогона модели этой константы. Например, для генератора синусоидального сигнала (рис. 10.1) такими параметрами могут являться амплитуда, период и начальная фаза сигнала. Если для данного экземпляра значение параметра должно быть отличным от указанного в определении класса, то это новое значение параметра указывают в вызове конструктора класса, например,

CsineGenerator Gen = new CsineGenerator Gen (Period=1/Frequency);

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

CBall Ball = new Cball(y=10);

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

С точки зрения видимости, внешние переменные и методы имеют уровень public, а все остальные конструкции - уровень protected.

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

Другим претендентом на звание «объект» является локальное поведение, т.е. деятельность, выполняемая в узле карты состояний. Локальное поведение может быть дискретным, т.е. вложенной картой состояний или непрерывным - системой алгебро-дифференциальных уравнений и формул. Конкретное поведение может быть использовано как деятельность более чем в одном узле карты состояний. Очевидно, что поведение имеет смысл только в контексте своего блока, т.к. в уравнениях, формулах и мгновенных действиях используются переменные блока. Таким образом, описание поведения можно рассматривать как внутренний (inner) класс в описании класса блока (см. например внутренние классы Java или C#).

С другой стороны, не существует никакой принципиальной разницы между локальным поведением и элементарным блоком. Непрерывное локальное поведение - это классическая динамическая система. Дискретное локальное поведение - это классическая карта состояний. Гибридная карта состояний и задает элементарный гибридный блок как «склейку» из классических блоков.

Пусть, например, нам нужно создать комбинированный генератор сигнала, который выдавал бы на выходе в зависимости от положения переключателя (перечислимая переменная Mode) либо синусоиду, либо пилообразный сигнал, либо ничего.

Можно написать формулу генерации синусоиды и нарисовать карту состояний для генерации «пилы» (см. рис. 10.3), но готовые отлаженные генераторы синусоиды и «пилы» (пусть эти классы называются CSinGen и CSanGen) имеются в библиотеке стандартных блоков любого пакета моделирования. Можно использовать главные поведения этих блоков как локальные для соответствующих состояний. Необходимо ассоциировать их внешние переменные с переменными используюшегося блока при создании экземпляра поведения. В данном примере выход комбинированного генератора используется вместо выходов Y в стандартных блоках:

block class CGenerator {

input enum{mNull,mSin,mSaw} Mode = mNull;

atatechart class main {

initial state NullGen entry actions (Y = 0);

state SinGen do CSawGen (Period=2, V as Y);

transition from NullGentoSinGen when Mode= =mSin;

transition from SinGentoNullGen when Mode!=mSin;

transition from NullGentoSawGen when Mode= =mSin;

transition from SawGen toNullGen when Mode!=mSin;

}/*main;