Глобальные данные применялись
в основном для организации связей между
подпрограммами, но они существенно ограничивали
гибкость подпрограмм. Действительно, если
подпрограмма работала с какими-то глобальными
данными, то эти данные должны были совпадать
по имени, типу во всех программах, которые
использовали данную подпрограмму.
В отличие от этого, локальные
данные, наоборот позволяли подпрограмме
быть независимой от программ, использующих
её. Но они не обеспечивали передачу данных
между различными подпрограммами. Для передачи
данных или ссылок на них использовался
механизм формальных/фактических параметров.
Об этом механизме надо сказать особо,
так как его значение в структурном программировании
велико.
Существо механизма формальных/фактических
параметров состоит в том, что при описании
подпрограммы объявляются формальные параметры,
которые она принимает при вызове. Когда
реально происходит вызов подпрограммы,
на место формальных параметров подставляются
фактические параметры того же типа. Это
позволило, в частности, сделать глобальные
переменные виртуальными. Действительно,
в место формального параметра можно было
передать значение любой переменной или
ссылку на любую переменную, указанного
в формальном параметре типа! Так, например,
если у нас было два одинаковых массива,
то механизм глобальных переменных не позволял
одной и той же подпрограмме работать поочерёдно
то с одним массивом, то с другим, поскольку
внутри подпрограммы использовался физический
адрес массива. Передача же ссылок на массивы
в подпрограмму решила эту проблему. Подпрограмма
теперь могла работать с любым массивом,
достаточно было ей передать ссылку на
нужный массив. Исходное положение массивов
в памяти перестало играть какую-либо роль
в работе подпрограммы. Виртуализация данных,
передаваемых в подпрограмму, имеет самое
непосредственное отношение к виртуализации
самих подпрограмм. Но об этом поговорим
чуть позже.
Образование библиотек подпрограмм
происходило, как правило, по функциональному
признаку. То есть, в одну библиотеку помещались
подпрограммы, выполняющие близкую по составу
работу. Например, библиотека подпрограмм,
работающих с каким-то устройством, или
библиотека подпрограмм, отображающих какие-то
формы. Библиотеки подпрограмм, безусловно,
были мощным стимулом в развитии программного
обеспечения и преодолении его сложности.
Однако они имели существенный недостаток.
Как бы не был хорошо отлажен код подпрограмм,
но результаты их работы могли быть неверны,
если входные данные имели неправильные
значения. Однако данные располагались
в программе и могли изменяться произвольно.
Любое неосторожное изменение данных могло
непредсказуемым образом изменить результат
работы программы. Появилась реальная потребность
объединить и защитить данные и код, который
их обрабатывает. Эта задача была решена
в модульном программировании. Модуль,
в отличие от библиотеки подпрограмм, мог
содержать не только подпрограммы, но и
данные.
Переход к модульному программированию
был логичным следствием развития идеологии
структурного программирования. Данные,
располагаемые в модуле, были глобальными
и для модуля и для программы, которая
использовала данный модуль. А, следовательно,
говорить о надёжности данных было преждевременно,
поскольку данные по-прежнему могли быть
изменены кодом, расположенным вне модуля.
Для увеличения безопасности данных модуль
разделили на несколько зон. Одна из зон
обеспечивала взаимодействие между модулем
и внешним программным обеспечением. Это,
так называемая, зона интерфейса. Другая
зона отвечала за реализацию модуля и была
недоступна для внешнего программного обеспечения.
Соответственно критичные к произвольным
изменениям данные помещались в зону реализации
и были недосягаемы для программы или других
модулей. Про эти данные можно сказать,
что они были инкапсулированы модулем.
В интерфейсной зоне оставались, как правило,
только те данные, с которыми активно взаимодействовало
внешнее программное обеспечение. Нельзя
сказать, что эти данные можно было произвольно
изменять без ущерба для работоспособности
программы, но «прятать» все данные в зоне
реализации означало, что подпрограммам,
расположенным в модуле, было бы трудно
взаимодействовать с другим программным
обеспечением. Выход был найден в том,
что практически все данные были инкапсулированы,
то есть, помещены в зону реализации, а
для взаимодействия с ними в интерфейсную
зону помещались объявления специальных
интерфейсных подпрограмм. Суть этих подпрограмм
состояла в том, что только они могли изменять
состояния переменных, расположенных внутри
модуля, или возвращать значения этих переменных.
Таким образом, можно было контролировать
в модуле любые изменения данных и перехватывать
некорректные действия до того, как они
привели к разрушению программы или выдаче
ошибочных результатов.
Однако перенос данных в
зону реализации поставил ещё одну серьёзную
проблему: данные необходимо было инициализировать
перед началом работы и освобождать перед
окончанием работы модуля. Например, перед
началом работы модуля могла потребоваться
динамически распределяемая область памяти,
а в конце работы требовалось вернуть эту
область памяти системе. Это привело к
появлению в модулях областей инициализации,
которые вызывались до начала работы модуля,
и областей, которые автоматически вызывались
перед тем, как модуль завершит работу.
Если теперь сложить все
элементы полученной мозаики воедино, то
получится знакомая картина: все подпрограммы
модуля собраны вместе по функциональному
признаку и работают над одними данными,
инкапсулированными в модуль; модуль имеет
области кода, которые вызываются при инициализации
и окончании работы модуля. Такой модуль
можно назвать прообразом объекта. Но для
получения более полного сходства необходимо
вернуться к виртуализации, о которой говорилось
выше.
|