лекция 11 (1161106), страница 2
Текст из файла (страница 2)
Понятие объектной ориентированности не сводится к понятию класса (обертка и тип одновременно), например, Ада 95 - модульный язык, но нет отдельного понятия класса (как обёртки), хотя есть ключевое слово class.
Модульные языки.
п.1. Понятие логического модуля
Трудно дать определение модуля, никакой четкой синтаксической структуры у модуля в разных яп нет. Модуль – это средство группировки, у модуля есть начало – заголовок, конец и внутреннее содержание. Только с точки зрения синтаксической структуры на модуль смотреть нельзя. Пример: рассмотрим условный оператор во многих яп,
if E then
S1
Else
S2
Endif
Тут тоже есть начало, конец и некоторое содержание, но модулем отдельные операторы никто не называет. Вот процедуру еще можно назвать модулем, пакет языка Ада точно можно назвать модулем.
Логический модуль (ЛМ) – то, что проще заимствовать, чем разрабатывать заново (неформальное определение из книги Кауфмана). С этой точки зрения при заимствовании операторов из кола мы их все-таки меняем («метод copy-paste» не рассматривается ), заимствование процедур, а особенно модулей, классов – распространено. Такие заимствования обычно не изменяются.
Итак модуль:
-
средство группировки ресурсов
-
механизм заимствования - для модуля всегда возникает понятие экспорта и импорта
При рассмотрении модуля как средства группировки – слово «логический» означает, то что мы абстрагируемся от разделения программы на физические модули. Физический модуль (ФМ) - понятие отличное от логического модуля. В Java ФМ - файл, ЛМ - класс и пакет. В Дельфи - ФМ и ЛМ совпадают, каждый unit находится в отдельном файле, и каждый фаил не может содержать больше одного unit’а. О взаимосвязях между логическими и физическими модулями будем еще говорит в главе посвященной раздельной компиляции. Сейчас речь пойдет только о ЛМ.
Рассмотрим простейший случай ЛМ, реализованный в Модуле-2 (похожее в Дельфи и Обероне).
1. Модули в Модуле-2 - очень важные понятия. Есть один главный модуль, и есть библиотечные модули (модули определений и модули реализаций). В модули определений содержатся только объявления структур данных и объявление операций, в модули реализаций - тела методов, вспомогательные процедуры, которые нужны только для реализации основных.
-
Главный модуль:
MODULE name;
.... объявления
BEGIN
операторы
END name.
-
Модули определения:
DEFINITION MODULE имя;
Только объявления
END имя.
-
Модуль реализации:
IMPLEMENTATION MODULE имя;
Для модуля реализации обязательно должен быть соответствующий модель объявлений, но не наоборот. Модуль реализаций не нужен, если например, в модуле определений нет ни одной процедуры. Все объявленные подпрограммы должны быть реализованы. Модуль реализации выступает как подчиненный модуль. Модуль определений содержит в себе только то, что нужно для использования. Тут мы уже видим понятие, с которым еще столкнемся позже - инкапсуляция (скрытие). Одна из главных целей появления логического модуля - понятие инкапсуляции. Такая структура скрывает от нас детали реализации.
Пример:
Модуль определений
DEFINITION MODULE Stacks;
CONST N = 50;
TYPE Stack = RECORD
body: ARRAY [1..N] OF Integer;
top: Integer;
END;
PROCEDURE Push (VARS: Stack; X:INTEGER);
PROCEDURE Pop (VAR S: Stack):Integer;
PROCEDURE Init (VAR S: Stack); // на что будет тор указывать
.............
VAR Done: Boolean; // true, если последняя операция успешна, false иначе
END Stacks;
Модуль реализаций
IMPLEMENTATION MODULE Stacks;
...
BEGIN
//Операторная часть - может отсутствовать, используется для инициализации некоторых статических объектов.
Done = true;
END Stacks. // Эта часть может отсутствовать, но в нашем случае мы инициализируем переменную Done.
…// расписываются все процедуры, которые мы описали и т.д.
Видно, что понятие модуля не сводиться только к определению новых тд, в то же время оно позволяет удобно сгруппировать как определения самой структуры, так и операции.
2. В языке Дельфи есть главная программа:
-
Главный модуль
program имя;
....
begin
end.
-
библиотечные модули имеют вид:
Unit имя; //тоже самое, что имя библиотечного модуля в модуле 2
interface
объявления
implementation
объявления и тела функций
begin
операторы
end.
То что в Модуле 2 было разбито на два модуля, здесь присутствует в одном, хотя логическое разделение осталось. Модули создаются, если одни и те же понятия используются в разных местах программы.
Взаимодействие модулей.
Главное понятие - это экспорт и импорт (имён и совокупность их характеристик). Одно из главных понятий - понятие пространства имён и области видимости. Самая простая форма пространства имен в Си - общее пространство имён (для внешних имён), локальное пространство - для каждого файла, это читая логика языка ассемблер. В языке ассемблера были директивы EXTRN и PUBLIC - для импорта и экспорта переменных соответственно. В Си - директива static (переменная доступна внутри файла), иначе - на все файлы. Риск конфликта имен был большой – поэтому при разработках больших библиотек в первую очередь договаривались о префиксах.
XWindowSystem – объектно-ориентированная система графических интерфейсов написанная на Си
-
X lib – интерфейс нижнего уровня
-
X Toolkit – объектно-ориентированная нашлепка - набор интерфейсных элементов и правила их построения
-
Motif - система пользовательского интерфейса
Это всё было написано на Си (сотни тысяч строк кода). Все имена имели префиксы - X, Xt, Xm соответственно.
В Си++ для разрешения конфликта имен было введено понятие пространства имен. В языках Модула-2 и Дельфи вводится понятие видимости. Видимость была прямая (непосредственная) и потенциальная (через имя соответствующего модуля - через уточнение, квалификатор Qualify). В ассемблерном варианте у нас была только непосредственная видимость.
-
В Си уточнение имеет вид: имя::имя
-
В остальных ЯП: имя(модуля).имя
В модель видимости Дельфи и Модула-2 основано на том, что глобальное пространство имен структурируется на две части – непосредственно видимая и потенциально видимая. Вначале непосредственно видимы только имена модулей. Имя главной программы вроде как видимо, только зачем оно нам нужно – имена модулей нужны для экспорта, а из главного модуля ничего не экспортируем. Кроме того, можем сказать, что имя главного модуля видимо, так как у нас не может быть другого модуля, чье имя совпадало бы с именем главного модуля. Все остальные имена, которые появляются в модулях определений (или интерфейсной части модуля для Дельфи) являются потенциально видимыми. Для того, чтобы их импортировать у нас есть специальные конструкции.
-
В языке Модула-2 есть две конструкции
А)IMPORT список_имён_модулей;
Все имена из соответствующих модулей-определений становятся потенциально видимыми. Пример: IMPORT Stacks - получаем доступ к пространству имён модуля Stacks. Имена из модуля определений – это те самые имена из глобального пространства имен, которые становятся видимы потенциально, а имена из модуля реализации видимы только внутри.
S: Stacks.Stack;
Stacks.Init(S);
Stacks.Push(S, 1);
X := Stacks.Pop(S);
Б)Существует вторая форма импорта:
FROM имя-модуля IMPORT список_имён;
Мы указываем, какие имена, из какого модуля мы импортируем; они становятся непосредственно видимыми из текущего модуля. Если конфликтуют имена из разных модулей, то это не ошибка – здесь главное, чтоб не конфликтовали имена модулей. Конфликтующие имена внутри модуля не могут быть непосредственно видимыми – они видимы только потенциально. Если свое имя конфликтует с импортированным, то свое видимо непосредственно, а импортированное потенциально, если оба чужих имени – то оба потенциально.
2. В Дельфах проще - там только есть предложение типа: Uses список_имён_модулей;
Все имена из интерфейсной части модулей становятся видимыми непосредственно. Такой подход упрощает программирование, но значительно ухудшает читаемость больших программ. Пусть есть большая библиотека – и множество модулей, как определить какое имя из какого модуля.
Как действовать в подобной ситуации в Модуле 2:
-
просмотреть стандартные идентификаторы
-
если имя чужое – то оно появляется здесь – (FROM имя-модуля IMPORT список_имён) или оно появляться (имя_модуля.имя)
-
если свое – то описано в данном модуле
т.е. поиск ведется только по тексту текущего модуля.
Чтобы облегчить поиск в Дельфи включались интерфейсы всех модулей и утилита grep. Если имена из модулей конфликтуют между собой, то используется уточненный импорт – имя_модуля.имя_объекта, но такой импорт не используют если конфликта нет.