Россия, Москва |
От Simula к Java и далее: основные ОО-языки и окружения
В официальных описаниях языков для одних и тех же или схожих концепций используются различные термины. Мы старались применять "родные" термины, когда они отражают особенности языка. Однако для простоты и согласованности используется и терминология, принятая в книге (как попытка унификации), когда различия не так важны. Например, мы говорим о подпрограммах, процедурах и функциях в языке Simula, хотя соответствующие термины в официальном использовании Simula - процедура, нетипизированная процедура и типизированная процедура.
Simula
Simula - это несомненный основатель Дома Классов (Дворца Объектов). Создание его было завершено (если не принимать во внимание небольшие более поздние обновления) в 1967 г. В это, возможно, трудно поверить: оформившийся ОО-язык существовал и был реализован до структурного программирования, до публикации Парнасом статей по скрытию информации, задолго до появления фразы "абстрактный тип данных". Война во Вьетнаме еще освещалась в газетах, мини-юбки еще могли возбуждать, а на северных берегах Балтики несколько удачливых разработчиков ПО, ведомые горсткой мечтателей, уже использовали силу классов, наследования, полиморфизма, динамического связывания и других прелестей ОО.
Основные понятия
Simula, по существу, является второй попыткой. В начале 60-х был разработан язык, известный как Simula 1, для поддержки моделирования дискретных событий. Хотя он не был ОО-языком в полном смысле термина, но суть он уловил. Собственно Simula - это Simula 67, созданный в 1967 г. Далом и Нигардом (Kristen Nygaard, Ole-Johan Dahl) из Университета Осло и Норвежского Компьютерного Центра (Norsk Regnesentral). Нигард потом объяснял, что решение сохранить название отражало связь с предыдущим языком и с сообществом его пользователей. К несчастью, это название долгое время для многих людей создавало образ языка, предназначенного только для моделирования событий, что было довольно узкой областью приложения, в то время как Simula 67 - это общецелевой язык программирования. Единственные его компоненты моделирования - это набор инструкций и библиотечный класс SIMULATION, используемый небольшим числом разработчиков Simula.
Название было сокращено до Simula в 1986 г., текущий стандарт датируется 1987 г.
Доступность
Simula часто представляется как респектабельный, но более не существующий предок. В действительности он еще жив и используется небольшим, но восторженным сообществом. Определение языка поддерживается Группой Стандартов Simula (Simula Standards Group). Существуют компиляторы и ПО от нескольких компаний, в основном скандинавских.
Основные черты языка
Рассмотрим в общих чертах основные свойства Simula. Автор не обидится, если читатель перейдет к следующему разделу о Smalltalk. Но чтобы полностью оценить объектную технологию, стоит потратить время на изучение Simula. Концепции представлены в их первоначальной форме, и некоторые возможности еще и теперь, спустя тридцать лет, не полностью использованы.
Simula - ОО-расширение языка Algol 60. Большинство правильных программ на Algol также являются правильными на Simula. В частности, основные структуры управления такие же, как в Algol: цикл, условный оператор, переключатель (низкоуровневый предшественник команды case в Pascal). Основные типы данных (целые, действительные и т. д.) тоже взяты из Algol.
Как и Algol, Simula использует на самом высоком уровне традиционную структуру ПО, основанную на понятии главной программы. Выполняемая программа - это главная программа, содержащая ряд программных единиц (подпрограмм или классов). Программная среда Simula поддерживает независимую компиляцию классов.
Simula использует структуру блока в стиле Algol 60: программные единицы, такие как классы, могут быть вложены друг в друга.
Все реализации Simula поддерживают автоматическую сборку мусора. Есть маленькая стандартная библиотека, включающая, в частности, двусвязные списки, используемые классом SIMULATION, изучаемым далее в этой лекции.
Как и в нотации этой книги, большинство общих сущностей, не относящихся к встроенным типам, обозначают ссылки на экземпляры класса, а не сами экземпляры. Однако это их явное свойство, подчеркиваемое нотацией. Тип такой сущности объявляется как ссылочный ref(C), а не просто C, для некоторого класса C. Для них используются специальные символы для присваивания, проверки на равенство и неравенство ( :-, ==, =/= ), в то время как целочисленные и действительные операнды используют другие символы для этих целей ( :=, =, /= ). Выше в одной из лекций даны обоснования за и против этого соглашения.
Для создания экземпляра используется выражение new, а не команда создания:
ref (C) a; ...;a :- new C
Выражение new создает экземпляр C и возвращает ссылку на него. Класс может иметь аргументы (играющие роль аргументов процедур создания в нашей нотации):
class C (x, y); integer x, y begin ... end;
В этом случае при вызове new следует передать соответствующие фактические аргументы:
a :- new C (3, 98)
Аргументы могут использоваться в подпрограммах класса. Но в отличие от возможности использования нескольких команд создания (конструкторов), данный подход дает только один механизм инициализации.
Кроме подпрограмм и атрибутов, класс может содержать последовательность инструкций, тело класса. Если тело существует, то вызов new будет выполнять его. Мы увидим, как использовать эту возможность, чтобы заставить классы представлять не пассивные объекты, как в большинстве других ОО-языков, а активные элементы, подобные процессам.
Механизм утверждений в языке не поддерживается. Simula поддерживает единичное наследование. Вот как класс B объявляется наследником класса A:
A class B; begin ... end
Для переопределения компонента класса в классе наследника, нужно просто задать новое объявление. Оно имеет приоритет над существующим определением (эквивалента оператора redefine нет).
Первоначальная версия Simula 67 не имела явных конструкций скрытия информации. В последующих версиях, компонент, объявленный как protected, недоступен клиентам. Защищенный компонент, объявляемый как hidden, недоступен потомкам. Незащищенный компонент может быть защищен потомком, но защищенный компонент не может экспортироваться потомками.
Отложенные компоненты задаются в форме "виртуальных подпрограмм", появляющихся в параграфе virtual в начале класса. Нет необходимости объявлять аргументы виртуальной подпрограммы, как следствие, разные эффективные определения виртуальной подпрограммы могут иметь разное число и типы аргументов. Например, класс POLYGON может начинаться так:
class POLYGON; virtual: procedure set_vertices begin ... end
позволяя потомкам задавать различное число аргументов типа POINT для set_vertices: три - для TRIANGLE, четыре - для QUADRANGLE и т. д. Эта гибкость подразумевает некоторую проверку типов во время выполнения.
Simula поддерживает полиморфизм: если B - потомок A, присваивание a1 :- b1 корректно для a1 типа A и b1 типа B. Довольно интересно, что попытка присваивания почти рядом: если тип b1 является предком типа a1, присваивание будет работать, если во время выполнения объекты имеют правильное отношение соответствия - источник является потомком цели. Если соответствия нет, то результатом будет ошибка во время выполнения, а не специальная величина, обнаруживаемая и обрабатываемая ПО (как при попытке присваивания). По умолчанию связывание статично, за исключением виртуальных подпрограмм. Поэтому если f - не виртуальный компонент, объявленный в классе A, a1.f будет обозначать A версию f, даже если есть другая версия в B. Можно при вызове насильно задать динамическое связывание через конструкцию qua1Qua (лат.) - где, через, с помощью., как в:
(a1 qua B). f
Конечно, теряется автоматическая адаптация операции к ее целевому объекту. Однако можно получить желаемое поведение динамического связывания (его можно считать изобретением Simula), объявляя полиморфные подпрограммы как виртуальные. Во многих рассмотренных примерах полиморфная подпрограмма не была отложенной, но имела реализацию по умолчанию с самого начала. Для достижения того же эффекта разработчик Simula добавит промежуточный класс, где подпрограмма виртуальна.
В качестве альтернативы использования qua, инструкция inspect дает возможность выполнять различные операции на сущности a1, в зависимости от фактического типа соответствующего объекта, обязательно представляющего собой потомка типа A, объявленного для a1:
inspect a1 when A do ...; when B do ...; ...
Этим достигается нужный эффект, но лишь при замороженном множестве потомков класса, что вступает в конфликт с принципом Открыт-Закрыт.