3.4 Формирование классов
Все объекты моделируемого мира соотносятся с программными объектами языка Стив. Чтобы определить агента моделируемого мира, Вы должны создать программный объект, или класс. Этот класс будет служить шаблоном, который определяет поведение агента. Этот раздел описывает, как создавать и использовать такие классы.
Раздел «Определение классов» (3.4.1) описывает, как определить пустой класс.
Все классы имеют два главных компонента: методы, которые определяют поведение класса, и переменные, которые определяют данные, относящиеся к данному классу. Эти данные могут содержать как информацию о состоянии агента, так и информацию, необходимую для вычислений. Раздел «Определение переменных» экземпляра (3.4.2) детализирует, как переменные можно добавить к объектам, в то время как раздел «Определение методов класса» (3.4.3) показывает, как определяются методы.
Два специальных метода являются ключевыми для поведения агентов: один, который вызывается автоматически при создании агента, init, и другой, который выполняется автоматически на каждом шаге моделирования, iterate. Эти методы, а также несколько других специальных методов, обсуждаются в разделе «Специальные имена методов (3.4.5).
Даже после того, как класс определён, он всё еще не будет присутствовать в процессе моделировании. Это потому, что класс – не что иное, как «шаблон» для агента. Чтобы включить агенты в процесс моделирования, Вы должны использовать форму создания экземпляров класса. Раздел «Создание и удаление экземпляров» (3.4.6) описывает, как экземпляры классов могут быть созданы и удалены.
3.4.1 Определение классов
При создании класса Вы обычно не начинается с нуля – вместо этого, Вы создаете новый класс как отпрыск существующего класса. Это называется созданием подкласса. При выделении подкласса, новый подкласс наследует все методы и переменные своего родительского класса. Такой подход означает, что большинство трудной работы будет проделано уже существующими классами breve, и мы можем просто исключить и настроить те аспекты поведения агента, которые нам необходимы.
Например, если мы формируем объект, который должен перемещаться в трёхмерном мире, мы хотели бы иметь объект, который понимает отношения между координатами, скоростью и ускорением. Вместо того, чтобы создавать такой класс самостоятельно, мы можем создать подкласс класса Mobile.tz, который включён в breve. Выбранный нами подкласс будет включать те действия, которые мы хотим в него заложить, в том время как родительский класс поддерживает подробности поведения.
При построении класса Вы должны прежде всего выбрать имя класса и его родительский класс. Родительский класс – это тот класс, от которого новый класс будет наследовать аспекты своего поведения. Классы, которые должны будут использоваться преимущественно для вычислений, и не требовать никаких специальных унаследованных аспектов поведения, будут обычно использовать универсальный корневой класс Object. Классы, которые перемещаются в пространстве, наследуют своё поведение от Mobile, в то время как неподвижные объекты в пространстве наследуют поведение от Stationary. Полный список классов дан в Приложении A.
Пустой класс обычно определяется следующим программным кодом на Стиве:
parent_class_name : class_name {
}
Поскольку мы часто имеем дело с классами в их множественной форме (подобно созданию многих экземпляров объекта), может быть полезным дать классу псевдоним, который позволит нам обращаться к классу в его множественной форме. Это не обязательно, но может сделать программу более лёгкой для чтения. Это псевдоним определяется путём добавления текста (aka alias_name) после имени класса.
В качестве примера определения класса, как с псевдонимом, так и без, рассмотрим класс, названный myMobile, который будет потомком класса Mobile:
# Сначала без псевдонима.
Mobile : myMobile {
}
# а теперь с псевдонимомом.
Mobile : myMobile (aka myMobiles) {
}
Этот программный текст определяет пустой класс без переменных и методов. Это означает, что он будет вести себя точно так же, как его родительский класс. Следующий шаг должен настроить поведение класса через добавление в него методов и переменных.
3.4.2 Определение экземплярных переменных класса
Экземплярная переменная – переменная, связанная с классом. Каждый экземпляр класса будет иметь свои частные копии экземплярных переменных класса.
Как только было написано объявление пустого класса, можно добавлять переменные, используя заголовок + variables, за котором следует список экземплярных переменных. Переменные перечисляются в формате variable_name (variable_type).
Имя переменной должно начинаться с символа, но последующие символы могут быть любыми алфавитно-цифровыми символами, так же как и символами _ и -.
Несколько переменных одного и того же типа могут быть также объявлены в одной и той же строке:
variable1, variable2, variable3, ... (variableType).
Типы переменных подробно описаны в разделе «Типы» (3.5).
В качестве примера, мы добавим несколько переменных к простому классу, который мы создавали в предыдущем разделе:
Mobile : myMobile {
+ variables:
myInt, myOtherInt (int).
myObject (object).
myFloat (float).
3.4.3 Определение методов
Самый простой метод, который можно вызвать в Стиве, – это вызвать метод, не имеющий никаких аргументов. Определить такой метод очень просто. Для этого мы создаём строку:
+ to methodName:
Операторы, которые следуют за этой строкой, будут частью только что определённого метода, и так до конца определения объекта, или до определения следующего метода.
Чтобы определить метод, который использует аргументы, мы должны будем записать ключевое слово, имя переменной и тип каждого аргумента. Ключевое слово идентифицирует переменную, когда она вызывается, в то время как имя переменной – то, как на переменную будут ссылаться внутри метода. Наконец, тип – это просто тип переменной, который будет ей придан. Данная информация располагается в следующем виде: keyword variable_name (type), так что метод, который имеет одну переменную, может быть определён следующей строкой:
+ to set-velocity to-value theValue (float):
Если метод использует две переменные, мы добавляем другую тройку ключевое слово/имя/тип:
+ to set-rotation of-joint theJoint (Object) to-value theValue (float):
Программный текст, связанный со вторым методом, тогда бы использовал переменные theJoint и theValue: "of-joint" и "to-value" – не действительные переменные, а применяемые вместо них ключевые слова, которые указывают, какие переменные следуют за ними.
Синтаксис вызова этого метода прост. После имени экземпляра и имени метода, мы даём список ключевых слов и значений, которые будут переданы им. Порядок следования пар ключевых слов / значений не влияет на то, как выполняется программа, хотя он может способствовать улучшению удобочитаемости программы. Следующие строки вызывают метод set-rotation, который мы определили выше:
# следующие строки эквивалентны
myObject set-rotation of-joint myJoint to-value 200.
myObject set-rotation to-value 200 of-joint myJoint.
Методы могут иметь также локальные переменные, связанные с ними. Вид определения таких переменных такой же, как и у определения переменной класса, за исключением того, что они следуют после строки определения метода, но не после строки определения переменной. Переменные метода автоматически обнуляются всякий раз, когда вызывается метод. Объявления переменных в методе должны предшествовать всем операторам в методе.
Например, вот простой метод, который использует локальные переменные:
+ to find-closest-creature in creatureList (list):
item (object).
closestItem (object).
distance (float).
# мы начинаем с необоснованного высокого значения"closestDistance" so that
#для того, чтобы мы были уверены, что найдётся что-нибудь ближе.
closestDistance = 1000000.
foreach item in creatureList: {
distance = |(self get-location) - (item get-location)|.
if distance < closestDistance: {
closestItem = item.
closestDistance = distance.
}
}
return closestItem.
Только для разработчиков
При изучении встроенных классов, включённых в поставку breve, Вы могли бы обратить внимание на некоторые методы, определённые с использованием знака «–», вместо знака «+»:
- to methodName:
Этот синтаксис просто означает, что метод должен считаться непубличным и что метод не должен быть задокументирован. Хотя эти методы функциональны, как и все остальные методы, их использование в пользовательских программах исключается.
3.4.4 Дополнительные аргументы методов
Определения методов могут также включать в себя спецификацию дополнительных аргументов. Дополнительные аргументы – это аргументы, которые имеют значения, присвоенные по умолчанию, и поэтому их необязательно описывать при вызове метода. Дополнительные аргументы могут очень упростить запись программы на Стиве.
Чтобы сделать аргумент дополнительным, Вы должны снабдить их значением, принимаем по умолчанию. Для этого Вы будете должны изменить определение аргумента, включив текст «= value» после имени аргумента. Например, переменной, названной theHeight с ключевым словом with-height можно было бы присвоить значение по умолчанию так: with-height theHeight = 100 (int). Значения по умолчанию для дополнительных аргументов должны быть литеральными (конкретными) значениями (а не выражениями или переменными).
Ниже – пример метода, определённого с набором дополнительных аргументов:
# Создание нового агента, с несколькими значениями по умолчанию.
+ to create-new-agent with-color color = (1, 0, 0) (vector)
with-energy energy = 100 (int)
with-radius radius = 10 (int)
with-name name = "agent" (string):
Метод, приведённый выше, можно вызвать множеством способов, произвольно включая или исключая каждый из аргументов:
# нет аргументов
self create-new-agent.
# некоторые из аргументов
self create-new-agent with-energy 10 with-name "Becky".
# все аргументы
self create-new-agent with-color (1, 1, 1)
with-energy 100
with-radius 20
with-name "Robert".
3.4.5 Специальные имена методов
Некоторые имена методов имеют специальное значение в Стиве, так как они вызываются автоматически в специальные моменты времени в ходе моделирования. Эти методы, в особенности init и iterate, являются ключевыми, поскольку они – точка входа для того, как Ваши агенты будут инициализированы и как они будут себя вести. Эти специальные имена методов выделены ниже:
init, если он существует, вызывается автоматически, когда создаётся экземпляр класса. Метод вызывается не только для класса, для которого создаётся экземпляр, но также и для его суперклассов и всех других предков вплоть до корневого объекта. Хотя Вы должны запускать метод init для Вашего класса, для которого устанавливается экземпляр, метод init не может быть вызван вручную.
iterate, если он существует, автоматически вызывается на каждой итерации breve-машины. Если Ваш класс должен решать задачу на каждой итерации, то Вы можете выбрать запуск метода iterate. Порядок, в соответствии с которым объекты в программе проходят каждую итерацию, не может контролироваться – если Вам необходимо контролировать порядок, в соответствии с которым выполняются действия, то используют iterate совместно с методом post-iterate, который описывается ниже.
В отличие от методов init и destroy, iterate не вызывается автоматически для суперклассов экземпляра. Это означает, что Ваш метод iterate должен вызывать iterate более высокого порядка, если Вы хотите присоединить метод iterate родительского класса. Это абсолютно необходимо для подклассов Control.
post-iterate, если он существует, автоматически вызывается на каждой итерации breve-машины после того, как метод iterate для всех объектов был вызван. Иногда желательно выполнять действия на каждой итерации, которые требуют данных, сформированных на той же самой итерации другими объектами. Если эти действия выполняются в методе iterate, то объект А не может быть уверен в том, что объект Б уже провёл соответствующие вычисления (и наоборот). Чтобы решить эту проблему, объекты могут запускать метод post-iterate, который автоматически вызывается, когда все объекты прошли итерацию. Демонстрационный пример PatchLife использует этот приём.
destroy, если он существует, вызывается автоматически, когда класс освобождается. Однако, в отличие от init, и так же, как для iterate, Вы должны явно вызывать метод destroy для класса более высокого порядка, если Вы хотите, чтобы он тоже был вызван. Если Ваш класс должен выполнить некоторые задачи прежде, чем быть аннулированным, Вы жолжны вызвать этот метод. Учтите, что Вам необходимо быть осторожными, чтобы не освободить объект, на который ссылаются в основном классе, если он необходим для метода destroy основного класса.
|