Пользователи предъявляют внешнее требование надежности услуг, а возможно (если они достаточно подготовлены), и целостности создаваемых сетей. Наша задача, как реализаторов комплекса услуг, перевести внешние требования (потребности) на язык реализационных возможностей.
Эти потребности и возможности относительны на каждом этапе проектирования. Скажем, потребность в надежности реализуется возможностью поддерживать целостность. Потребность в целостности реализуется возможностью обеспечить (строго) регламентированный доступ к создаваемым сетям.
Итак, будем считать, что технологическая потребность пользователя в том, чтобы иметь в распоряжении класс данных "сети", иметь возможность создавать сети в нужном количестве и получать регламентированный доступ к каждой созданной сети.
Варианты и противоречия. Казалось бы, такую потребность несложно удовлетворить - достаточно предоставить пользователю соответствующий тип данных. Давайте так и поступим.
Объявим регулярный тип "сети", все объекты которого устроены аналогично массиву "сеть":
(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 |


