Copyright (C) А.Гавва V-0.4w май 2004

4. Контекст, видимость и подсистемы

4.1 Контекст и видимость

Ошибочное понимание разницы между контекстом (scope) и видимостью (visibility) может вызвать трудности у многих программистов, которые знакомятся с языком программирования Ада. Следует учесть, что понимание этого различия имеет важное значение при использовании Ады для разработки программного обеспечения. Спецификатор совместности контекста with помещает соответствующий библиотечный модуль в контекст, однако, ни один ресурс этого модуля не становится непосредственно видимым для клиента. Это является основным различием от директивы #include семейства языков C или директивы uses в реализации Паскаль-системы фирмы Borland. Ада предоставляет множество способов обеспечения непосредственной видимости различных ресурсов, после того как они помещены в контекст. Разделение контекста и видимости является важным концептом разработки программного обеспечения. Однако, следует заметить, что этот концепт редко реализовывается в различных языках программирования. Примечательно также, что идея пространства имен namespace, принятая в C++, подобна концепции контекста и видимости Ады.

Каждая инструкция и/или каждая конструкция языка имеет объемлющий контекст. Обычно, контекст легко увидеть в исходном тексте, поскольку контекст имеет какую-либо точку входа (описательная часть declare, идентификатор подпрограммы, идентификатор составного типа, идентификатор пакета...) и какую-либо явную точку завершения. Как правило, явная точка завершения контекста обозначается с помощью соответствующей инструкции end. Таким образом, как только мы обнаруживаем в исходном тексте какую-либо инструкцию end, мы понимаем, что она является точкой завершения какого-нибудь контекста. Контекст может быть вложенным. Например, одна процедура может быть описана внутри другой процедуры.

Несколько иначе выглядит ситуация, когда спецификатор совместности контекста with помещает в контекст какой-либо библиотечный модуль. В этом случае в контекст помещаются все ресурсы указанного библиотечного модуля, но при этом, спецификатор with не делает эти ресурсы непосредственно видимыми.

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

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

4.2 Управление видимостью

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

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

Необходимо обратить внимание на то, что данные проблемы подобны, вне зависимости от того - рассматриваем-ли мы типы, пакеты или классы наследования. Таким образом, некоторые свойства могут быть:

В качестве примера, можно привести следующую аналогию: то что на фасаде дома - то видно всем (public), то что внутри дома - то видно только членам семьи (private), то что в чьем-либо теле - не видно никому (internal).

Благодаря существованию трех уровням видимости, существуют три уровня управления:

Следует заметить, что различие между приватными и внутренними описаниями не требовалось в Ada 83. Необходимость в таком различии появилась в Ada95, поскольку появилась новая концепция - концепция дочерних модулей, и сопутствующая ей необходимость в управлении уровнями видимости.

4.3 Подсистемы

Большие системы обычно состоят из сотен и даже тысяч самостоятельных компонентов. Обычно не требуется, чтобы каждый отдельный компонент активно взаимодействовал со всеми остальными частями системы. Таким образом, отдельные компоненты собираются в группы или кластеры модулей, которые интенсивно взаимодействуют между собой. При этом, взаимодействие отдельного компонента с остальной системой не так активно или вообще отсутствует. Подобные группы компонентов называют подсистемами (subsystem), и они могут собираться, управляться и обслуживаться изолировано от остальной системы.

Для поддержки построения и облегчения сопровождаемости больших систем, Ada95 предоставляет концепцию дочерних библиотечных модулей. Любой вид библиотечных модулей (подпрограммы, пакеты...) может быть описан как дочерний модуль библиотечного пакета (или настраиваемого модуля). Это позволяет строить иерархии библиотечных модулей. Каждый дочерний модуль (потомок) имеет свою приватную часть и тело, и способен "видеть" приватные описания своего предка, что аналогично текстовой вставке спецификации дочерних модулей в спецификацию пакетов родителей, предоставляя возможность раздельной компиляции их спецификаций и обеспечивая большую гибкость в управлении видимостью.

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

Дочерний модуль может быть приватным модулем. Таким образом, его использование будет ограничено телами модулей той же самой иерархии, и будет доступно для спецификаций и тел других приватных модулей той же самой иерархии. Это является дополнительным уровнем управления для обеспечения возможности изоляции внутренних свойств подсистемы в спрятанных модулях, которые известны и совместно используются только внутри иерархически связанной группы библиотечных модулей.

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

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

Использование иерархических модулей предусматривает улучшенное управление пространством имен. Предположим, что кто-то приобрел два семейства программных компонентов. Одно, у Mr. Usable, который написал пакет Strings, для строковой обработки. Другое, у Mr. Reusable, которое также включает пакет с именем Strings. В такой ситуации, при одновременном использовании обеих семейств программных компонентов, возникает коллизия имен в пространстве имен библиотеки. С другой стороны, если для семейств компонентов предусмотрены префиксы Usable.Strings и Reusable.Strings, то коллизии имен не происходит. Это дает нам возможность сконцентрировать свои усилия на управлении пространством имен для подсистем или семейств компонентов вместо управления плоским пространством имен для всех компилируемых модулей.

Такое структурирование пространства имен можно обнаружить в предопределенном окружении Ады: теперь, все предопределенные пакеты являются дочерними модулями пакетов Ada, System и Interfaces (с соответствующими библиотечными переименованиями для поддержки обратной совместимости с Ada 83).

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

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

Рассмотрим пример операционной системы (например POSIX). Очевидно, что никто не желает иметь один большой пакет с сотнями типов и операций. Должно существовать логическое группирование типов и операций. В Ada95 фактически, некоторые приватные типы, которые должны быть совместно используемы, больше не являются помехой для подобного разделения операций, благодаря видимости приватных описаний, которая обеспечена предками для дочерних модулей. Иерархические модули позволяют логическое структурировани и группирование, вне зависимости от используемых приватных описаний типов.

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

Предположим, что существуют две подсистемы. Например, Radar и Motor. Между разными группами разработчиков, при разработке своих подсистем, не будет возникать коллизия имен "вспомогательных пакетов". Например, для Radar.Control и Motor.Control. Таким образом, для разных групп разработчиков упрощаются описания подсистем и облегчается разработка, а также значительно облегчаются (или полностью исчезают) проблемы общей интеграции.


Copyright (C) А.Гавва V-0.4w май 2004