В.Ш. Кауфман - Языки программирования - концепции и принципы (1990) (1160787), страница 11
Текст из файла (страница 11)
По-прежнему не будем заботиться о диагностике ошибок, когда связей
оказывается слишком много (больше макс_связей). Но если два узла просят
связать вторично, то будем такой запрос игнорировать. Следует учесть также,
что требование связать узел с самим собой вполне законно.
procedure связать(X, Y: in узел) is
begin
if not есть_связь(X, Y) then
установить_связь(X, Y) ;
if X /= Y then
установить_связь(Y, X) ;
end if ;
end if ;
end связать ;
Мы ввели вспомогательную функцию есть_связь с очевидным эффектом
(возможно, ее полезно и пользователю предоставить) и вспомогательную
процедуру установить_связь, которая призвана вносить изменения в массив
"узлы" своего первого аргумента. Ключевое слово not - это знак отрицания
(унарная логическая операция). Продолжим детализацию.
function есть_связь(X, 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 установить_связь ;
Таким образом, количество связей увеличивается на единицу и в качестве
последней связи записывается имя узла "куда".
Вопрос. Нельзя ли переименование указать вне процедур и функций, чтобы
не повторять его?
Подсказка. В переименовании участвуют (динамические) параметры.
Итак, все услуги реализованы. Осталось выписать полное тело пакета. Для
экономии места и времени позволим себе не выписывать объявления процедур и
функций полностью, обозначая пропуски многоточием. Обратите внимание на
порядок следования объявлений. Он существенен, соответствует правилу
последовательного определения. Но оно касается лишь вспомогательных
объявлений, введенных в теле пакета. Имена из спецификации пакета считаются
объявленными ранее.
package body управление_сетью is
type запись_об_узле is
record
включен : BOOLEAN : = false;
связан : связи;
end record;
сеть : array(узел) of запись_об_узле;
function узел_есть(X : узел) return BOOLEAN is
........
function все_связи(X : узел) return связи is
........
procedure вставить(X : in узел) is
........
procedure переписать(в_узле : in узел, после : in индекс_узла)
........
procedure чистить(связь : узел, в_узле:узел) is
........
procedure удалить(X : in узел) is
........
function есть_связь(X, Y : узел) return BOOLEAN is
........
procedure установить_связь(откуда, куда : in узел) is
........
procedure связать(X, Y : in узел) is
........
end управление_сетью;
Подчеркнем, что тип запись_об_узле, объект "сеть", процедуры
"переписать", "чистить", "установить_связь", функция "есть_связь" недоступны
пользователю, так как объявлены в теле, а не в спецификации пакета.
Третий шаг детализации завершен. Осталась прокомментировать полученный
результат.
2.6. Принцип раздельного определения, реализации и использования услуг
(принцип РОРИУС)
Итак, мы написали три сегмента: спецификацию пакета управление_сетью,
процедуру построение_сети и тело пакета управление_сетью. Важно понимать
роли этих сегментов в жизненном цикле программы.
В них воплощен принцип раздельного определения, реализации и
использования услуг (РОРИУС). По существу это рациональное применение
абстракции на различных этапах проектирования. Проектируя определение
пакета, отвлекаемся от деталей его возможного использования и вариантов
реализации. Проектируя использование пакета, отвлекаемся от деталей
определения и, тем более, реализации. Проектируя реализацию, отвлекаемся от
несущественного (с точки зрения реализации) в определении и использовании.
Упражнение. Приведите конкретные примеры деталей, несущественных при
определении, реализации и использовании соответственно.
Каждая названная абстракция представлена своим материальным воплощением
- отдельным модулем. Каждый из трех сегментов является модулем - законченным
продуктом интеллектуальной деятельности в том смысле, что его можно записать
в библиотеку и (при наличии документации) использовать без помощи автора и
без жесткой связи с остальными модулями.
Три наших модуля, однако, не являются полностью независимыми.
Центральным служит, конечно, модуль определения, т.е. спецификация пакета.
Оставляя спецификацию неизменной, можно выбирать варианты реализации (тело
пакета), не заставляя изменять использование (процедуры, аналогичные
процедуре "построение_сети"). И это только благодаря тому, что реализация
защищена от несанкционированного доступа при использовании - из процедуры
построение_сети нельзя непосредственно добраться, например, до массива
"сеть" и нарушить дисциплину его эксплуатации операциями пакета. С другой
стороны, никакое изменение реализации (согласованное со спецификацией и
содержательным внешним эффектом объявленных услуг) не в состоянии повлиять
на использование иначе, как по части ресурсоемкости (расходование времени,
памяти и других ресурсов). Наконец, можно строить произвольные, одновременно
существующие и по-разному развивающиеся использующие модули, не тратя
ресурсов на однажды определенные и реализованные услуги.
Таким образом, рассмотренные разновидности модулей, воплощающие
абстракции определения, использования и реализации услуг, удовлетворяют
важнейшую технологическую потребность - проектировать, испытывать и хранить
программы по частям, отдельными модулями.
2.7. Принцип защиты абстракций
Кроме РОРИУС, в связи с этой потребностью необходимо указать на еще
один важный принцип - ПРИНЦИП ЗАЩИТЫ АБСТРАКЦИЙ (от разрушения). Одно из его
проявлений в Аде - доступ к телу пакета исключительно через имена,
объявленные в спецификации. Именно благодаря этому пользователь получает
абстрактный объект - сеть, которой может пользоваться, но не может ее
разрушить. Абстрактность сети проявляется в том, что пользователь не знает
деталей ее представления и реализации доступа.
Принцип защиты абстракций обслуживают и другие конструкты Ады (в
частности, приватные типы).
Обратите внимание, что создав пакет управление_сетью, мы в сущности
спроектировали ПОЯ, построив подходящую модель предметной области (модель
сети связи). Тем самым показали, как пользоваться Адой в качестве базового
ЯП. При этом средством абстрактного определения ПОЯ служит спецификация
пакета, средством конкретизации - тело пакета, а средством защиты -
невидимость из использующих сегментов имен, объявленных в теле пакета.
3. Важнейшие абстракции: данные, операции, связывание
3.1 Принцип единства и относительности трех абстракций
Планируя поведение исполнителя (составляя программу) мы, в конечном
итоге, планируем его действия над некоторыми объектами. Необходимо
обозначить во-первых, конкретный объект - данное, во-вторых, конкретное
действие - операцию, и, в-третьих, обозначить конкретные условия, при
которых нужно указанное действие совершить над указанным объектом (т.е.
обозначить условия связывания данных и операций). Таким образом, в описании
акта исполнителя выделяются составляющие, играющие три различные роли:
данных, операций и связывания. Подчеркнем, что в каждом конкретном акте
(когда указана операция, указаны данные и созрели условия для связывания)
три названные роли неразрывны.
Однако программировать было бы невозможно, если бы не удалось разорвать
единство этих ролей и рассматривать данные, в нужной степени абстрагируясь
от конкретных операций; рассматривать операции, в нужной степени
абстрагируясь от конкретных данных, над которыми они выполняются; и,
наконец. рассматривать связывание, в нужной степени абстрагируясь от данных
и операций, которых оно касается.
Полученное путем такой абстракции понятие операции отражает активное
начало в поведении исполнителя, понятие данного - пассивное начало, а
понятие связывания - управляющее (организующее) начало - отражает
всевозможные виды управления.
Первые две роли выделяют и обсуждают чаще. Однако значение связывания
никак не меньше. Как будет показано, оно отражает не только разнообразные
способы управления, но и разнообразные способы конкретизации в
программировании.
Важно понимать, что указанные три роли полностью различны лишь в рамках
конкретного акта исполнителя. Когда же речь идет о каком-либо отдельно
взятом объекте, то в зависимости от ситуации или точки зрения он вполне
может выступать в различных ролях (иногда - в любой из этих ролей). В этом
смысле указанные роли (как и любые другие) относительны. Например, знак "+"
чаще всего воспринимается как обозначение операции сложения. Вместе с тем он
выступает в роли данного, когда фигурирует как элемент формулы, которую
переводят в польскую инверсную запись. Но этот же знак и связывает два
операнда формулы, между которыми он помещен, предвосхищая их совместное
участие в одном акте исполнителя. Этот акт будет выполнен при условии, что
управление достигло именно рассматриваемого знака "+".
Относительность и единство (взаимосвязь, взаимозависимость) трех
выделенных ролей - глубокая закономерность. Проявляется она, в частности, в
том, что для достаточно развитого оформления каждой абстракции привлекаются
и две другие. Однако в этом случае они играют подчиненную роль, роль
обслуживающего средства, роль инструмента для выделения и (или) реализации
основной абстракции.
Рассмотрим ряд примеров, подтверждающих принцип относительности и
единства выделенных абстракций. Покажем, что он не только объясняет
появление и смысл языковых конструктов, но и указывает перспективы их
развития.
Так, хороший стиль программирования предполагает, что когда вводят
операционную абстракцию (процедуру, функцию, операцию), то явно указывают
характеристики данных, к которым ее можно применять (с которыми ее можно
связывать). Например, специфицируют параметры процедуры (как мы и поступали
в задаче об управлении сетью). Такую спецификацию можно назвать
спецификацией операции по данным извне (со стороны использования). Когда
реализуют введенную операционную абстракцию (проще говоря, программируют
процедуру или функцию), то также указывают характеристики данных (на этот
раз используемых, подчиненных, желательно локальных; в нашем примере это был
тип запись_об_узле). Такую спецификацию можно назвать спецификацией операции
по данным изнутри (со стороны реализации).
Вопрос. Когда полезна такая спецификация?
Подсказка. Ведь это, в сущности, заказ ресурсов, который необходим для
их рационального распределения, особенно в режиме разделения ресурсов между
параллельными процессами.
В современных ЯП (в том числе в Аде), когда вводят абстракцию данных
(переменнную, тип переменнных) то указывают класс операций, связываемых с
этой абстракцией (определяют АТД). Это можно назвать спецификацией данных по
операциям извне (со стороны использования). В нашем примере это пять
операций - вставить, удалить, связать, все_связи и узел_есть, арактеризующих
доступ к абстрактной переменной "сеть". Из соображений симметрии следовало
бы рассматривать и данные (абстракции данных), для реализации которых нужны
внутренние (локальные) операции. Это была бы спецификация данных по
операциям изнутри.
Вопрос. Зачем могут понадобиться такие спецификации?
Подсказка. Представьте "живые" данные, отражающие состояние
взаимодействующих процессов. Какие операции потребуются, чтобы их
реализовать? Какой должна быть среда, в которую можно перенести такие
данные? Конкретный пример - монитор Хоара-Хансена, о котором пойдет речь в
главе об асинхронных процессах. Для его реализации требуются операции над
сигналами, семафорами или рандеву.
3.2. Связывание
Так как выделение связывания в качестве самостоятельной абстракции
менее традиционно, полезно рассмотреть его подробнее и проиллюстрировать
плодотворность этого выделения нетривиальными применениями.
В самом общем понимании связывание неотличимо от установления
соответствия, сопоставления, отношения. Однако в применении к
программированию нас интересует определенный набор выразительных средств,
управляющих связыванием и пригодных для реализации на компьютерах. Поэтому,
говоря о связывании здесь, мы подразумеваем аппарат, действующий в рамках
существующей или потенциальной системы программирования и предназначенный
для организации взаимодействия операций и данных.
Постараемся показать, во-первых, что абстракция связывания помогает с
единых позиций понимать кажущиеся совершенно различными сущности и, во-
вторых, что выделение такой абстракции помогает увидеть возможные
направления развития ЯП.