Добавил:
ivanov666
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз:
Предмет:
Файл:Языки программирования. Концепции и принципы
.pdf
Пример современного базового ЯП (модель А)
сеть : array (узел) of запись_об_узле;
61
Мы написали ОБЪЯВЛЕНИЕ ОБЪЕКТА. Как всякое объявление объекта,
оно связывает имя («сеть») с характеристиками того объекта данных, который
в дальнейшем будет значением (денотатом) объявленного имени. В нашем случае
этот объект – одномерный массив с компонентами типа запись_об_узле, доступ
ными по индексам типа узел.
Шаг 3.2. Следует заняться уточнением того, как устроен объект типа запись_
об_узле. Естественно считать, что это некоторая структура данных, куда вносятся
сведения о том, включен ли узел в сеть, если да, то какие узлы с ним связаны.
Объявим тип запись_об_узле.
type запись_об_узле is record
включен : BOOLEAN := false;
связан : связи;
end record;
Итак, каждая запись об узле состоит из двух полей. Поле с именем «включен»
и начальным значением false служит признаком включения узла в сеть, а поле
с именем «связан» содержит все связи узла.
Шаг 3.3. Теперь все готово, чтобы заняться операциями над сетью. Начнем
с функции узел_есть.
Уточним ее внешний эффект: она должна быть применима к любому объекту
типа «узел» и должна выдавать результат true, если узел с таким именем есть
в сети, и false в противном случае.
Мы сформулировали ее содержательный эффект. Такого рода сведения о фун
кции узел_есть должны быть в пользовательской документации. Это необходи
мое для пользователя дополнение к спецификации (заголовку функции), указан
ной в спецификации пакета в строке 18. Но сейчас нас интересует реализация
функции. Поэтому следует обеспечить ее содержательный эффект в реализацион
ных терминах, в частности через представление сети (которое пользователю недо
ступно и даже может оказаться неизвестным). Было бы естественным выдавать
в качестве результата просто значение поля «включен» записи об узле. Но для
этого на всю остальную реализацию пакета необходимо наложить единое требо
вание (если угодно, определить дисциплину работы с этим полем): его значением
в любой компоненте массива «сеть» после выполнения любого действия должно
быть true, если узел есть в сети, и false в противном случае. При выполнении этого
требования необходимый содержательный внешний эффект функции узел_есть
обеспечивается следующим объявлением (определением):
function óçåë_åñòü(Õ : óçåë) return BOOLEAN is
begin
return сеть(X).включен;
end узел_есть;
Обратите внимание, в полном определении функции повторена ее спецификация.
ОПЕРАТОР ВОЗВРАТА (return) завершает исполнение тела функции, дос
тавляя в качестве ее результата значение указанного выражения. В нашем случае

62
Современное состояние языков программирования
это ВЫБОРКА (поля «включен» из записи, находящейся в массиве «сеть» по ин
дексу, указываемому значением формального параметра «X»).
Шаг 3.4. Займемся реализацией функции все_связи. Ее содержательный внеш
ний эффект – проявление связей узла. При соответствующей дисциплине работы
с сетью ее реализация могла бы быть такой:
function все_связи(Х : узел) return связи is
begin
return сеть(X).связан;
end все_связи;
Вопрос. В чем должна состоять требуемая дисциплина?
К такой функции можно обращаться лишь тогда, когда известно, что узел
в сети есть, иначе можно выбрать неопределенное значение в поле «связан».
Шаг 3.5. Реализация процедуры «вставить» (с очевидным содержательным
эффектом) может выглядеть так:
procedure вставить(X : in узел) is
begin
сеть(X).включен := true;
сеть(X).связан.число := 0;
end вставить;
Теперь займемся процедурами «удалить» и «связать». Они чуть сложнее за
счет того, что нужно вносить изменения в несколько компонент массива «сеть».
Шаг 3.6. Содержательный эффект процедуры «удалить» очевиден: узел с ука
занным именем должен быть удален из сети и (с учетом требования поддерживать
целостность сети) все связи, в которых он участвовал, должны быть ликвидиро
ваны.
Такого содержательного эффекта можно достичь многими способами. Здесь
естественно учесть то, что пользователи лишены возможности непосредственно
изменять «сеть» (например, явными присваиваниями этому массиву), они могут
к нему добираться только посредством объявленных в спецификации пакета про
цедур и функций. Наша задача как реализаторов пакета – обеспечить согла
сованный внешний эффект объявленных услуг (при этом внутренний эффект
процедур и функций можно варьировать).
Другими словами, действие процедуры «удалить» на массив «сеть» должно
быть таким, чтобы функции узел_есть и все_связи выдали результаты, согласо
ванные с содержательным представлением об отсутствии узла в сети. Один вари
ант реализации – присвоить false соответствующему полю «включен» и подпра
вить поле «связан» во всех узлах, с которыми был связан удаляемый узел. Другой
вариант – в этой процедуре поле «связан» не подправлять, но изменить реализа
цию функции все_связи так, чтобы перед выдачей результата она приводила поле
«связан» в соответствие с полем «включен».
Это и есть варианты упоминавшихся выше дисциплин работы с сетью.
Рациональность выбора одного из вариантов неочевидна. Если часто удаляют
узлы и редко просят все их связи, может оказаться выгодным второй вариант,

Пример современного базового ЯП (модель А)
63
иначе – первый. Оценить частоту запросов – дело непростое. Поэтому возмож
ность менять реализацию (подстраиваясь к условиям эксплуатации), не меняя
внешнего эффекта, может оказаться очень важной.
Обратите внимание, не спроектировав представления данных, мы не могли
начать проектировать процедуры. А теперь видим, что проектирование данных
может зависеть от дисциплины взаимодействия операций. В этом – одно из про
явлений принципа единства основных абстракций, о котором мы еще поговорим.
Выберем первый вариант реализации.
procedure удалить(X : in узел) is
begin
сеть(X).включен := false;
for i in 1..сеть(Х).связан.число loop
чистить(Х,сеть(Х).связан.узлы (i));
end loop;
end;
Понадобилась процедура «чистить», которая должна убрать в узле, указанном
вторым параметром, связь с узлом, указанным первым параметром.
procedure чистить(связь : узел, в_узле : узел) is
begin
for i in 1..сеть(в_узле).связан.число loop
if сеть(в_узле).связан.узлы (i) = связь then
переписать(в_узле, после => i);
end if;
end loop;
end чистить;
Осталось спроектировать процедуру «переписать» – она должна переписать
связи в указанном узле, начиная с номера «после», и уменьшить на единицу общее
число связей этого узла.
procedure переписать(в_узле : in узел, после : in индекс_узла) is
запись:связи renames сеть(в_узле).связан;
begin
запись.число := запись.число – 1;
for j in после..запись.число loop
запись.узлы(j) := запись.узлы(j+1);
end loop;
end переписать;
Здесь мы впервые воспользовались ОБЪЯВЛЕНИЕМ ПЕРЕИМЕНОВА
НИЯ (renames), чтобы сократить имена и сделать их более наглядными. Этот же
прием можно было применять и раньше. Напомним, что о диагностике ошибок мы
пока не заботимся (предполагается, что перед применением процедуры «уда
лить» всегда применяется функция узел_есть, чтобы не было попытки удалить
несуществующий узел).
«Запись» – это имя объекта типа «связи» (объекта сеть(в_узле).связан), локаль
ное для процедуры «переписать». Общий вид ОБЪЯВЛЕНИЯ ПРОЦЕДУРЫ:

64
<спецификация процедуры> is
<локальные объявления>;
begin
<операторы>;
end процедуры;
Современное состояние языков программирования
Оборот for j in <диапазон> – это ОБЪЯВЛЕНИЕ УПРАВЛЯЮЩЕЙ ПЕРЕ
МЕННОЙ ЦИКЛА, область действия которой – от объявления до конца цикла.
Внутри БАЗИСНОГО ЦИКЛА (от loop до end loop) j считается постоянной. Если
диапазон пуст (это бывает, когда его правая граница меньше левой), базисный
цикл не выполняется ни разу. Иначе он выполняется при всех последовательных
значениях j из указанного диапазона, если только выполнение всего оператора
цикла не будет досрочно завершено оператором выхода (exit).
В нашем случае все имена узлов из массива «узлы» с индексами от «после+1»
до «число» перемещаются на позиции с предыдущим индексом. В результате мас
сив «узлы» содержит все старые связи, кроме вычеркнутой, а их общее количество
предварительно скорректировано (уменьшено на 1).
Шаг 3.7. Содержательный эффект процедуры «связать» также очевиден: она
применима к включенным в сеть узлам; после ее применения узлы считаются свя
занными.
Снова можно было бы реализовать такой эффект поразному. Выберем следу
ющий способ, учитывающий конкретные реализации остальных наших процедур:
в запись о каждом из аргументов процедуры «связать» будем добавлять указание
о связи с другим аргументом.
Попрежнему не будем заботиться о диагностике ошибок, когда связей оказы
вается слишком много (больше макс_связей). Но если два узла просят связать
вторично, то будем такой запрос игнорировать. Следует учесть также, что требо
вание связать узел с самим собой вполне законно.
procedure связать(X, Y: in узел) is
begin
if not есть_связь(Х, Y) then
установить_связь(Х, Y);
if X /= Y then
установить_связь(У, X);
end if;
end if;
end связать;
Мы ввели вспомогательную функцию есть_связь с очевидным эффектом (воз
можно, ее полезно и пользователю предоставить) и вспомогательную процедуру
установить_связь, которая призвана вносить изменения в массив «узлы» своего
первого аргумента. (Ключевое слово not – это знак отрицания (унарная логическая
операция))
.
Продолжим детализацию.
function есть_связь(Х, Y : узел) return BOOLEAN is
запись : связи renames сеть(X).связан;

Пример современного базового ЯП (модель А)
begin
for i in 1..запись.число loop
if запись.узлы(i) = Y then
return true;
end if;
end loop;
return false;
end есть_связь;
procedure установить_связь(откуда, куда : in узел) is
запись : связи renames сеть(откуда).связан;
begin
запись.число := запись.число+1;
запись.узлы(запись.число) := куда;
end установить_связь;
65
Таким образом, количество связей увеличивается на единицу, и в качестве по
следней связи записывается имя узла «куда».
Вопрос. Нельзя ли переименование указать вне процедур и функций, чтобы не
повторять его?
Подсказка. В переименовании участвуют динамические параметры.
Итак, все услуги реализованы. Осталось выписать полное тело пакета. Для
экономии места и времени позволим себе не выписывать объявления процедур и
функций полностью, обозначая пропуски многоточием.
Обратите внимание на порядок следования объявлений. Он существен, соот
ветствует правилу последовательного определения. Но оно касается лишь вспо
могательных объявлений, введенных в теле пакета. Имена из спецификации паке
та считаются объявленными ранее.
package body управление_сетью is
type запись_об_узле is
record
включен : BOOLEAN : = false;
связан: связи;
end record;
сеть : array (узел) of запись_об_узле;
function узел_есть(Х : узел) return BOOLEAN is
......
function все_связи(Х : узел) return связи is
......
procedure вставить (X : in узел) is
......
procedure переписать(в_узле : in узел, после : in индекс_узла)
......
procedure чистить(связь : узел, в_узле:узел) is
......
procedure удалить(Х : in узел) is
......

66
function есть_связь(Х, Y : узел) return BOOLEAN is
......
procedure установить_связь(откуда, куда : in узел) is
........
procedure связать(X, Y : in узел) is
........
end управление_сетью;
Подчеркнем, что тип запись_об_узле, объект «сеть», процедуры «переписать»,
«чистить», «установить_связь», функция «есть_связь» недоступны пользовате
лю, так как объявлены в теле, а не в спецификации пакета.
Третий шаг детализации завершен. Осталась прокомментировать полученный
результат.
Современное состояние языков программирования
2.7. Принцип раздельного
определения, реализации
и использования услуг
(принцип РОРИУС)
Итак, мы написали три сегмента: спецификацию пакета управление_сетью, про
цедуру построение_сети и тело пакета управление_сетью. Важно понимать роли
этих сегментов в жизненном цикле программы.
В них воплощен принцип раздельного определения, реализации и использо
вания услуг (РОРИУС). По существу, это рациональное применение абстракции
на различных этапах проектирования.
Проектируя определение пакета, отвлекаемся от деталей его возможного ис
пользования и вариантов реализации.
Проектируя использование пакета, отвлекаемся от деталей определения и тем
более реализации.
Проектируя реализацию, отвлекаемся от несущественного (с точки зрения ре
ализации) в определении и использовании.
Упражнение. Приведите конкретные примеры деталей, несущественных при опре
делении, реализации и использовании соответственно.
Каждая названная абстракция (определение, использование, реализация)
представлена своим материальным воплощением – отдельным модулем. Ведь
каждый из трех сегментов является модулем – законченным продуктом интел
лектуальной деятельности в том смысле, что его можно записать в библиотеку и
(при наличии документации) использовать без помощи автора и без жесткой свя
зи с остальными модулями.
Три наших модуля, однако, не являются полностью независимыми. Централь
ным служит, конечно, модуль определения, то есть спецификация пакета. Остав
ляя спецификацию неизменной, можно выбирать варианты реализации (тело па

Пример современного базового ЯП (модель А)
кета), не заставляя изменять использование (процедуры, аналогичные процедуре
«построение_сети»). И это только благодаря тому, что реализация защищена от
несанкционированного доступа при использовании – из процедуры построение_сети
нельзя непосредственно добраться, например, до массива «сеть» и нарушить дис
циплину его эксплуатации операциями пакета. С другой стороны, никакое изме
нение реализации (согласованное со спецификацией и содержательным внешним
эффектом объявленных услуг) не в состоянии повлиять на какиелибо характери
стики использования, кроме ресурсоемкости (расхода времени, памяти и других
ресурсов). Наконец, можно строить произвольные, одновременно существующие
и развивающиеся, поразному использующие модули, не тратя ресурсов на од
нажды уже определенные и реализованные услуги.
Таким образом, рассмотренные разновидности модулей, воплощающие абст
ракции определения, использования и реализации услуг, удовлетворяют важней
шую технологическую потребность – проектировать, использовать и хранить
программы по частям, отдельными модулями.
67
2.8. Принцип защиты абстракций
Кроме РОРИУС, в связи с только что отмеченной потребностью необходимо ука
зать на еще один важный принцип – принцип защиты абстракций (от разруше
ния). Одно из его проявлений в Аде – доступ к телу пакета исключительно через
имена, объявленные в спецификации. Именно благодаря этому пользователь по
лучает абстрактный объект – сеть, которой может пользоваться, но не может ее
разрушить. Абстрактность сети проявляется в том, что пользователь не знает де
талей ее представления и реализации доступа. Принцип защиты абстракций об
служивают и другие конструкты Ады (в частности, приватные типы).
Обратите внимание, что, создав пакет управление_сетью, мы в сущности спро
ектировали ПОЯ, построив подходящую модель предметной области (модель
сети связи). Тем самым показали, как пользоваться Адой в качестве базового ЯП.
При этом средством абстрактного определения ПОЯ служит спецификация паке
та, средством конкретизации – тело пакета, а средством защиты – невидимость из
использующих сегментов имен, объявленных в теле пакета.


Глава 3
Важнейшие абстракции:
данные, операции,
связывание
3.1. Принцип единства
и относительности
трех абстракций .............................. 70
3.2. Связывание .............................. 71
3.3. От связывания к пакету ............. 72
3.4. Связывание и специализация ... 74
3.5. Принцип цельности................... 79

70
Современное состояние языков программирования
3.1. Принцип единства
и относительности трех абстракций
Планируя поведение исполнителя (составляя программу), мы в конечном итоге
планируем его действия над некоторыми объектами. Необходимо обозначить, во
первых, конкретный объект – данное, вовторых, конкретное действие – опера
цию и, втретьих, обозначить конкретные условия, при которых нужно указанное
действие совершить над указанным объектом (то есть обозначить условия связыва
ния данных и операций). Таким образом, в описании акта исполнителя выделяются
составляющие, играющие три различные роли: данных, операций и связывания.
Подчеркнем, что в каждом конкретном акте (когда указана операция, указаны
данные и созрели условия для связывания) три названные роли неразрывны.
Однако программировать было бы невозможно, если бы не удалось разорвать
единство этих ролей и рассматривать данные, в нужной степени абстрагируясь от
конкретных операций; рассматривать операции, в нужной степени абстрагируясь
от конкретных данных, над которыми они выполняются; и, наконец, рассматри
вать связывание, в нужной степени абстрагируясь от данных и операций, которых
оно касается.
Полученное путем такой абстракции понятие операции отражает активное на
чало в поведении исполнителя, понятие данного – пассивное начало, а понятие
связывания – управляющее (организующее) начало – отражает всевозможные
виды управления.
Первые две роли выделяют и обсуждают чаще. Однако значение связывания
никак не меньше. Как будет показано, оно отражает не только разнообразные спо
собы управления, но и разнообразные способы конкретизации в программиро
вании.
Важно понимать, что указанные три роли полностью различны лишь в рамках
конкретного акта исполнителя. Когда же речь идет о какомлибо отдельно взятом
объекте, то в зависимости от ситуации или точки зрения он вполне может высту
пать в различных ролях (иногда – в любой из этих ролей). В этом смысле указан
ные роли(как и любые другие) относительны. Например, знак «+» чаще всего вос
принимается как обозначение операции сложения. Вместе с тем он выступает
в роли данного, когда фигурирует как элемент формулы, которую переводят
в польскую инверсную запись. Но этот же знак связывает два операнда формулы,
между которыми он помещен, предвосхищая их совместное участие в одном акте
исполнителя. Этот акт будет выполнен при условии, что управление достигло
именно рассматриваемого знака «+».
Относительность и единство (взаимосвязь, взаимозависимость) трех выделен
ных ролей – глубокая закономерность. Проявляется она, в частности, в том, что
для достаточно развитого оформления каждой абстракции привлекаются и две
другие. Однако в этом случае они играют подчиненную роль, роль обслуживаю
щего средства,роль инструмента для выделения и (или) реализации основной аб
стракции.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]
