Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Языки программирования. Концепции и принципы

.pdf
Скачиваний:
2
Добавлен:
08.09.2026
Размер:
2 Мб
Скачать
☆
Пример современного базового ЯП (модель А)
сеть : 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. Принцип единства и относительности трех абстракций
Планируя поведение исполнителя (составляя программу), мы в конечном итоге планируем его действия над некоторыми объектами. Необходимо обозначить, во первых, конкретный объект – данное, вовторых, конкретное действие – опера цию и, втретьих, обозначить конкретные условия, при которых нужно указанное действие совершить над указанным объектом (то есть обозначить условия связыва ния данных и операций). Таким образом, в описании акта исполнителя выделяются составляющие, играющие три различные роли: данных, операций и связывания. Подчеркнем, что в каждом конкретном акте (когда указана операция, указаны данные и созрели условия для связывания) три названные роли неразрывны.
Однако программировать было бы невозможно, если бы не удалось разорвать единство этих ролей и рассматривать данные, в нужной степени абстрагируясь от конкретных операций; рассматривать операции, в нужной степени абстрагируясь от конкретных данных, над которыми они выполняются; и, наконец, рассматри вать связывание, в нужной степени абстрагируясь от данных и операций, которых оно касается.
Полученное путем такой абстракции понятие операции отражает активное на чало в поведении исполнителя, понятие данного – пассивное начало, а понятие связывания – управляющее (организующее) начало – отражает всевозможные виды управления.
Первые две роли выделяют и обсуждают чаще. Однако значение связывания никак не меньше. Как будет показано, оно отражает не только разнообразные спо собы управления, но и разнообразные способы конкретизации в программиро вании.
Важно понимать, что указанные три роли полностью различны лишь в рамках конкретного акта исполнителя. Когда же речь идет о какомлибо отдельно взятом объекте, то в зависимости от ситуации или точки зрения он вполне может высту пать в различных ролях (иногда – в любой из этих ролей). В этом смысле указан ные роли(как и любые другие) относительны. Например, знак «+» чаще всего вос принимается как обозначение операции сложения. Вместе с тем он выступает в роли данного, когда фигурирует как элемент формулы, которую переводят в польскую инверсную запись. Но этот же знак связывает два операнда формулы, между которыми он помещен, предвосхищая их совместное участие в одном акте исполнителя. Этот акт будет выполнен при условии, что управление достигло именно рассматриваемого знака «+».
Относительность и единство (взаимосвязь, взаимозависимость) трех выделен ных ролей – глубокая закономерность. Проявляется она, в частности, в том, что для достаточно развитого оформления каждой абстракции привлекаются и две другие. Однако в этом случае они играют подчиненную роль, роль обслуживаю щего средства,роль инструмента для выделения и (или) реализации основной аб стракции.
Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]