Как происходит отслеживание свободного экстента? |
Создание объектов и выполняемых систем
Не каждое объявление должно создавать объект
Чтобы избежать void-вызова и возникновения исключения в последнем примере, мы можем изменить процедуру создания build_a_line так, чтобы перед вызовом fancy_line.highlight объект уже был создан и присоединен к fancy_line. Вскоре мы это и сделаем. Но стоит задаться вопросом, зачем вообще нужны void-ссылки? Не следует ли полагать, что объявление, такое как
fancy_line: LINE
при выполнении будет иметь эффект создания объекта — экземпляра LINE — и присоединения его к fancy_line?
Ответ — нет. Есть несколько причин, по которым в момент создания ссылки инициализируются значениями void и требуется явное создание объектов в вашей программе.
Основная причина в том, что некоторые объекты просто не существуют. Это справедливо и в обычном мире. Человек может иметь супруга, но не у каждого он есть. ПО, моделирующее наш мир, должно учитывать такие свойства. В классе PERSON, появляющемся в ПО, управляющем налогами, вероятно, будет создан компонент, идентифицирующий супруга,
spouse: PERSON
для которого полезна void-ссылка, представляющая случай отсутствия супруга. Даже если предположить, что все женаты и все замужем, то и в этом случае не имеет смысла создавать объект spouse каждый раз, когда создается объект PERSON. Такая попытка привела бы к бесконечной череде создания объектов, поскольку каждый супруг является экземпляром класса PERSON и, следовательно, имеет поле spouse, которое является объектом класса PERSON, которое. и так далее.
Поэтому разумное решение состоит в том, чтобы инициализировать объекты значением void и создавать объекты явно в том момент, когда они понадобятся или появляются естественным образом.
Рассмотрим наш пример детальнее. Когда человек имеет супруга, то возникает ограничение взаимной пары: у супруга есть супруг, и тогда ссылки должны быть зависимыми — картинка показывает это лучше слов:
Формула говорит это даже лучше, чем рисунок: инвариант класса PERSON должен включать предложение
monogamy: (spouse /= Void) implies (spouse.spouse = Current)
Ключевое слово Current обозначает текущий объект, формальное определение которого будет дано чуть позже в этой лекции. Инвариант класса содержательно говорит, что если некто имеет супруга, то супруг супруга является тем самым некто.
Так как Current обозначает текущий уже существующий объект, то он никогда не может быть void. Отсюда, в частности, следует: (spouse /= Void) implies (spouse.spouse /= Void). Если вы замужем, то ваш муж женат. Не следует недооценивать преимущества таких, казалось бы, банальностей. Разработка ПО включает прояснение интуитивных знаний о проблемной области и их формализацию, например, в инвариантах класса.
Еще одно наблюдение о вышеприведенном инварианте класса: если вы внимательно следили за обсуждением полустрогих булевских операций, то должны заметить, что этот инвариант требует полустрогой версии импликации, так как второй операнд не будет определен для spouse, имеющего значение Void.
Все это обсуждение показывает, почему не следует немедленно создавать объект после объявления сущности. Оба объекта на предыдущем рисунке должны начинать свою жизнь самостоятельно, не вступая до поры до времени в брак, сохраняя свои ссылки spouse как void.
Позднее, при выполнении, например, некоторой команды marry, произойдет взаимное присоединение spouse-ссылок, и объекты будут присоединены друг к другу, переходя в состояние, показанное на предыдущем рисунке. Такие команды взаимного присоединения не создают новых объектов — они просто присоединяют ссылки к существующим объектам.
Роль void-ссылок
Рассмотрим ссылку, появляющуюся в поле объекта, такую как поле spouse объекта person. Если ссылка присоединена к объекту, то это указывает на присутствие некоторой информации, представленной этим объектом. Если же ссылка — void, то это означает, что информация не существует. В частности, это крайне полезно при связывании объектов в сложные структуры. Многие интересные структуры данных, например, связные списки, играющие важнейшую роль в наших обсуждениях, основаны на этой концепции связывания.
Рассмотрим простой пример из Traffic. Мы можем решить представлять линию метро (любой экземпляр класса LINE) одним или несколькими экземплярами класса STOP, каждый из которых представляет остановку на линии. Один из возможных приемов (увидим и другие) состоит в том, чтобы у каждого экземпляра STOP существовало поле right, указывающее на следующую остановку. Так что экземпляр класса STOP может выглядеть примерно так:
где затушеванная часть представляет поля, обеспечивающие другую информацию об остановках. Тогда линия метро будет представлена множеством таких объектов, каждый из которых, кроме последнего, связан со следующим ссылкой right:
Заметьте, как последний объект использует void-ссылку, чтобы показать, что у него нет right-объекта справа. Завершение таких структур — одно из принципиальных использований ссылок void.
Имя right для поля, содержащего ссылку на следующий объект, идет от стандартного способа изображения таких структур с элементами, следующими друг за другом слева направо.
Вызовы в выражениях: помогут победить ваш страх перед void
До того как вернуться к созданию объектов, — что позволяет создавать не-void-ссылки, — следует взглянуть на общую схему использования ссылок в выражениях и попытаться избежать любого страха перед void-вызовами.
Поскольку вызов метода определен только для не-void-цели, то можно задуматься: а как выразить условие, чтобы оно всегда было определено, даже если оно включает вызов? Условному оператору, так же как и инварианту класса, может потребоваться условие в форме
fancy_line.count >= 6
Условие говорит, что у fancy_line по меньшей мере 6 станций. Но это только в том случае, если линия fancy_line присоединена к объекту. Если уверенности нет, то необходим способ выражения в неформальных терминах, а это верно, если и только если
"fancy_line определена, and then имеет по меньшей мере 6 станций"
Вы уже знаете решение (я надеюсь, что "and then" послужило звонком): полустрогие операции спроектированы для условий, в которых одна часть имеет смысл, только если другая часть имеет значение True (для and then) или False (для or else).
Теперь можно корректно записать это условие как
(fancy_line /= Void) and then (fancy_line.count >= 6)
Это гарантирует, что условие всегда определено:
- если fancy_line имеет значение void, результатом является False. Вычисление выражения не будет использовать второй операнд, который стал бы причиной исключения;
- если fancy_line присоединена, то второй операнд определен и будет вычислен, что даст окончательное значение результата выражения.
Другой вариант этого образца использует импликацию implies, которая определена как полустрогая операция (наряду с and then и or else). Условие в форме
(fancy_line /= Void) implies (fancy_line.count >= 6)
выражает слегка отличное условие:
"Если fancy_line определена, то следует, что у нее по меньшей мере 6 станций"
В отличие от and then, дающей False, импликация говорит, что "если не определено, то тоже ничего страшного", и дает значение True когда первый операнд ложен. Такой образец часто полезен в инвариантах класса. Он встречался при рассмотрении класса PERSON:
monogamy: (spouse /= Void) implies (spouse.spouse = Current)
Из условия следует, что если вы замужем (женаты), то супруг супруга — это вы сами. Но если супруга нет, то результат все равно True В противном случае, холостяки нарушали бы инвариант, к чему нет никаких оснований. Операция импликации implies корректно работает в этой ситуации, так как False влечет True ее полустрогость гарантирует отсутствие прерываний при вычислении условия во всех случаях.