Пользователи предъявляют внешнее требование надежности услуг, а возможно (если они достаточно подготовлены), и целостности создаваемых сетей. Наша задача, как реализаторов комплекса услуг, перевести внешние требования (потребности) на язык реализационных возможностей.

Эти потребности и возможности относительны на каждом этапе проектирования. Скажем, потребность в надежности реализуется возможностью поддерживать целост­ность. Потребность в целостности реализуется возможностью обеспечить (строго) ре­гламентированный доступ к создаваемым сетям.

Итак, будем считать, что технологическая потребность пользова­теля в том, чтобы иметь в распоряжении класс данных "сети", иметь возможность создавать сети в нужном количестве и получать регламентированный доступ к каждой созданной сети.

Варианты и противоречия. Казалось бы, такую потребность не­сложно удовлетворить - достаточно предоставить пользователю соот­ветствующий тип данных. Давайте так и поступим.

Объявим регулярный тип "сети", все объекты которого устроены аналогично массиву "сеть":

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

Теперь объект "сеть" можно было бы объявить так:

(б) сеть: сети;

Так же может поступить и пользователь, если ему понадобятся, например, две сети:

(в) сеть1, сеть2: сети;

и к его услугам два объекта из нужного класса.

Но возникает несколько вопросов.

Во-первых, мы подчеркивали, что объект "сеть" недоступен поль­зователю непосредственно (это и гарантировало целостность). Точ­нее, имя этого объекта не было видимо пользователю. А чтобы пи­сать объявления вида (в), пользователь должен явно выписать имя "сети". Другими словами, объявление типа "сети" должно быть ви­димым пользователю!

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

Но видимые извне пакета объявления должны находиться в его спецификации. Куда же именно в спецификации пакета следует по­местить объявление (а)? Вспомним правило последовательного оп­ределения. В (а) использованы имена типов "узел" и запись_об_узле. Но последний пока не объявлен в спецификации нашего пакета. Значит, нужно объявить и этот тип (после типа "связи", т. е. после строки 11), затем поместить (а).

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

Для этого перепишем строки с 13 по 18 спецификации пакета:

(13') procedure вставить (X : in узел, в_сеть : in out сети);

Обратите внимание, режим второго параметра in out! Указанная им сеть служит обновляемым параметром (результатом работы про­цедуры "вставить" служит сеть со вставленным узлом).

(14') procedure удалить (X : in узел, из_сети : in out сети);

(15') procedure связать (А, В : in узел, в_сети : in out сети);

(17') function узел_есть (X: узел, в_сети: сети) return BOOLEAN;

(18') function все_связи (X : узел, в_сети : сети) return связи;

Так как нужно обеспечить и возможность работать по-старому, с одной сетью, то в спецификации пакета следует оставить строки и 13-18, и 13'-18'. Тогда в соответствии с правилами перекрытия опе­раций в зависимости от заданного количества аргументов будет вы­зываться нужная спецификация (и, конечно, нужное тело) опера­ции. Например, написав

удалить (33);

вызовем строку 14 (и соответствующее тело для этой процедуры), а написав

удалить (33, сеть1);

или лучше

удалить (33, из_сети => сеть1);

вызовем строку 14' (и еще не созданное нами тело для этой проце­дуры).

Однако пора вспомнить о принципиальной проблеме, которая, возможно, уже давно мучает вдумчивого читателя. Ведь теперь не гарантирована целостность сетей!

Для того мы и скрывали "сеть" в теле пакета, чтобы пользователь не мог написать, например,

сеть(33).связан. число:= 7;

и нарушить тем самым дисциплину работы с сетью так, что последующее выполнение процедуры

удалить (33);

(в которой есть цикл по массиву связей узла 33) может привести к непредсказуемым последствиям.

Введя объявление (в), пользователь может нарушить целостность объекта сеть1 оператором

сеть1(33).связан. число := 7;

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

Итак, основное противоречие в том, что скрыть объявление типа "сети" в теле пакета нельзя - ведь его нужно оставить видимым пользователю (чтобы он мог объявлять новые сети), а оставить это объявление полностью открытым также нельзя - пользователь может нарушить целостность сетей.

Вот если бы ввести тип так, чтобы его имя было видимо, а строе­ние объектов - невидимо (т. е. разделить его спецификацию и реали­зацию)! Тогда технологическая потребность в регламентированном доступе была бы полностью удовлетворена.

Эта красивая и естественная идея в Аде воплощена в концепции ПРИВАТНЫХ типов данных (в Модуле-2 - в концепции так назы­ваемых "непрозрачных" типов данных).

4.3.2. Приватные типы данных

Подумаем о выразительных средствах, реализующих концепцию регламентированного доступа к данным определенного типа.

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

Так что было бы естественным аналогично оформить абстракцию данных - поместить в спецификацию пакета то, что должно быть ви­димым (что может играть роль минимальной "спецификации ти­па"), а в тело пакета упрятать полное определение типа.

В Аде минимальная "спецификация типа" воплощена конструктом "объявле­ние приватного типа", например:

(a') type сети is private;

а также перечнем спецификаций применимых операций.

Полное определение приватного типа по аналогии с определения­ми операций кажется естественным поместить в тело пакета. (Имен­но так сделано в Модуле-2.)

Почти так и нужно поступать в Аде. Но полное объявление при­ватного типа приходится помещать не в тело пакета, а в "полузак­рытую" (ПРИВАТНУЮ) часть спецификации пакета, отделяемую от открытой части ключевым словом private.

Спецификация обновленного пакета, который назовем "управление_сетями", может выглядеть следующим образом:

package управление_сетями is

... -- как и раньше; строки 2-11.

type сети is private;

... -- операции над сетями

... -- строки 13-18.

... -- строки 13'-18'.

private

type запись_об_узле is

record

включен : bollean := false;

связан : связи;

end record;

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

end управление_сетями;

В общем случае спецификация пакета имеет вид

package имя_пакета is

объявления_видимой_части

[private

объявления_приватной_части ]

end имя_пакета;

Квадратные скобки указывают, что приватной части может и не быть (как, например, в пакете управление_сетью).

Зачем же в Аде понадобилась приватная часть? Почему нет пол­ной аналогии между операционными абстракциями и абстракцией данных? (В языке Модула-2 эта аналогия выдержана полностью.) На эти вопросы ответим позже.

Семантика приватной части проста. Эту часть можно считать "резидентом тела пакета" в его спецификации. В теле пакета непос­редственно доступны все имена, объявленные в спецификации паке­та, в том числе и в приватной части. С другой стороны, в использующих этот пакет сегментах видимы только объявления открытой ча­сти спецификации.

Напишем один из таких использующих сегментов - процедуру две_сети:

with управление_сетями; use управление_сетями;

procedure две_сети is

сеть1, сеть2 : сети;

begin

вставить (13, в_сеть => сеть1 );

вставить (33, в_сеть => сеть1 );

связать (13, 33, в_сети => сеть1);

сеть2 := сеть1; -- присваивание полных объектов!

. . .

end две_сети;

Когда управление дойдет до места, отмеченного многоточием, бу­дут созданы две сети: "сеть1" и "сеть2", с узлами 33 и 13, причем эти узлы окажутся связанными между собой.

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

Вопрос. Нельзя ли испортить сеть за счет того, что доступен тип "связи"? Ведь становится известной структура связей указанного узла.

Подсказка. А где средства для несанкционированного изменения этой структуры?

Итак, концепция регламентированного доступа в Аде воплощена разделением спецификации и реализации услуг (разделением специ­фикации и тела пакета), а также приватными типами данных.

Доступ к "приватным" данным возможен лишь посредством опе­раций, объявленных в ОПРЕДЕЛЯЮЩЕМ ПАКЕТЕ. Для любого определяемого типа данных (не только приватного) так называется пакет, где расположено объявление этого типа данных (для типов "сети", "узел", "связи" и др. таковым служит пакет управление_сетями). Невозможен доступ, основанный на знании строения объек­тов (т. е. выборкой или индексацией), - это строение скрыто в при­ватной части и в использующих сегментах неизвестно.

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