Обратите внимание, в спецификации пакета указаны лишь спе­цификации (заголовки) процедур и функций. В таком случае их те­ла следует поместить в ТЕЛО ПАКЕТА, о котором пойдет речь в следующем разделе.

На этом закончим предварительное знакомство с Ада-конструктами.

2.6. Как пользоваться пакетом управление_сетью

Пусть нужно построить сеть из пяти узлов (13, 33, 25, 50, 90) и шести дуг (13, 33), (33, 25), (33, 50), (33, 90), (13, 50) и (25, 90). (Нарисуйте такую сеть.)

Это можно сделать следующей процедурой построение_сети:

with управление_сетью;

use управление_сетью;

procedure построение_сети is

begin

вставить(ЗЗ); вставить(13);

связать(33, 13); вставить(25);

связать (33,25); вставить (50);

вставить (90); связать (33,50);

связать(33,90); связать(13,50);

связать (25,90);

end построение_сети;

Первые две строки позволяют пользоваться услугами, предостав­ляемыми пакетом управление_сетью так, как будто все услуги объ­явлены непосредственно перед третьей строкой.

Как уже сказано, строка с ключевым словом with называется УКАЗАНИЕМ КОНТЕКСТА (with clause). Указание контекста де­лает видимыми (доступными по ПОЛНЫМ именам) все услуги, объ­явленные в пакетах, перечисленных вслед за with. Например, к про­цедуре "вставить" можно было бы обратиться так:

управление_сетью. вставить(...);

а объявить переменную типа "связи" можно так:

А : управление_ сетью. связи;

Строка с ключевым словом use называется УКАЗАНИЕМ СО­КРАЩЕНИЙ (use clause). Это указание позволяет пользоваться ви­димыми именами, не предваряя их именем пакета. Так мы и посту­пили в процедуре построение_сети. Подчеркнем, что указание со­кращений действует только для уже видимых имен. Его обязательно нужно предварять указанием контекста.

НЕ нашли? Не то? Что вы ищете?

Если нужно напечатать сведения о построенной сети, то это мож­но сделать следующими операторами (будем считать, что предопре­делены процедуры новая_строка (переход на новую строку при печа­ти) и "печать" (целого числа или массива)):

новая_строка;

for i in узел loop

if узел_есть (i) then

печать (i);

печать (все_связи (i).узлы);

end if;

новая_строка;

end loop;

Будет напечатано

13 33 50

25 33 90

33 13 25 50 90

50 33 13

90 33 25

Обратите внимание, тип "узел" используется для указания диа­пазона изменения значений переменной цикла. В нашем случае тело цикла выполнится 100 раз.

Третий шаг детализации - тело пакета. До сих пор мы смотрели на наш комплекс услуг с точки зрения потенциального пользовате­ля. Теперь настало время реализовать те услуги, которые мы объя­вили в спецификации пакета. В терминах Ады это означает, что нужно спроектировать ТЕЛО ПАКЕТА управление_сетью. Создавать тело пакета будем также пошаговой детализацией.

Шаг 3.1. Неважно, с детализации какой процедуры или функции начинать, - ведь ни одну из них нельзя написать прежде, чем не станет понятно, как представлена сама сеть, с которой нужно рабо­тать. Поэтому начнем с проектирования представления данных. Займемся представлением сети.

Есть много вариантов такого представления (таблица, список, пе­ремешанная таблица и т. п.). Выберем представление сети массивом:

сеть : array (узел) of запись_об_узле;

Мы написали ОБЪЯВЛЕНИЕ ОБЪЕКТА. Как всякое объявление объекта, оно связывает имя ("сеть") с характеристиками того объек­та данных, который в дальнейшем будет значением (денотатом) объ­явленного имени. В нашем случае этот объект - одномерный массив с компонентами типа запись_об_узле, доступными по индексам типа узел.

Шаг 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) завершает исполнение тела функции, доставляя в качестве ее результата значение указанного выражения. В нашем случае это ВЫБОРКА (поля "включен" из за­писи, находящейся в массиве "сеть" по индексу, указываемому значением формального параметра "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 соответствующему полю "включен" и подправить поле "свя­зан" во всех узлах, с которыми был связан удаляемый узел. Другой вариант - в этой процедуре поле "связан" не подправлять, но изме­нить реализацию функции все_связи так, чтобы перед выдачей ре­зультата она приводила поле "связан" в соответствие с полем "включен".

Это и есть варианты упоминавшихся выше дисциплин работы с сетью.

Рациональность выбора одного из вариантов неочевидна. Если ча­сто удаляют узлы и редко просят все их связи, может оказаться вы­годным второй вариант, иначе - первый. Оценить частоту запросов - дело непростое. Поэтому возможность менять реализацию (подстра­иваясь к условиям эксплуатации), не меняя внешнего эффекта, мо­жет оказаться очень важной.

Обратите внимание, не спроектировав представления данных, мы не могли начать проектировать процедуры. А теперь видим, что про­ектирование данных может зависеть от дисциплины взаимодействия операций. В этом - одно из проявлений принципа единства основных абстракций, о котором мы еще поговорим.

Выберем первый вариант реализации.

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) чтобы сократить имена и сделать их более наглядными. Этот же прием можно было применять и раньше. Напомним, что о диагностике ошибок мы пока не заботимся (предполагается, что пе­ред применением процедуры "удалить" всегда применяется функция узел_есть чтобы не было попытки удалить несуществующий узел).

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24