div.main {margin-left: 20pt; margin-right: 20pt} М. Безверхов. vasilisk@nm.ru
Компонентное программирование.
Глава 4. Предварительные определения.
Немного выше говорилось, что компонентное программирование –
естественный эволюционный этап развития программостроения. А его понятия –
результат эволюции каких-то очень хорошо известных понятий. Вот и
попробуем эволюционно проследить ту область, в которой должны возникнуть
определения этих понятий.
Пра-пра-предок «компоненты» назывался «подпрограмма» или
«процедура». Это был фрагмент исполняемого кода, который можно было
рассматривать как черный ящик – задавая снаружи процедуры параметры и
получая результаты её работы. Т.е. «процедура» обладала некоторой степенью
замкнутости, «отличности от других» и возможности её многократного
использования. Процедурное программирование здорово преуспело в развитии
структурного подхода к логическому конструированию программ и в разделении
труда программистов разных областей знания. «Библиотеки стандартных
программ» - лучшие памятники той эпохе и «компонентной организации труда»
программистов.
«Процедура» обладала рядом имманентных недостатков,
например, она не обозначала семантики. Хотя сама была семантически
несколько большим понятием, чем любая другая синтаксическая конструкция.
Не удивительно, что в «процедурную эпоху» программисты имели дело,
преимущественно, со стандартными арифметическими и алгебраическими типами
данных – целым, вещественным, комплексным числами, массивами из них. Если
всё это и можно было назвать «кирпичами конструкции», то были они слишком
мелкими и вряд ли могли быть названы «компонентами». Тип данных
«структура» появился в этой эпохе под самое её окончание и никакой роли
фактически не сыграл.
Зато он сыграл свою роль далее – при помощи «структуры
данных» появилась возможность производить новые типы, которые не были
встроенными в язык. Появилась возможность описать понятие произвольного
«объекта» как совокупности данных и некоторого индивидуального поведения
этих данных, возникли соответствующие языки.
Только вот сам «объектно-ориентированный подход» (ООП)
зародился прежде всего как философия и технология моделирования сложных
систем. А появление языков объектно-ориентированного программирования
стало результатом своеобразной конвергенции одновременных движений «от
моделирования» и «от процедурного языка». Примитивно говоря – и
постановщикам и программистам очень хотелось иметь язык, который бы
непосредственно позволял записывать сами модельные конструкции.
Практика выявила, что «объект» в смысле ООП оказался
семантически намного более самостоятельной конструкцией, чем «процедура» и
«структура данных» вместе взятые. А методология ООП – оказалась очень
естественной в смысле человеческого способа мышления. Поэтому пра-предком
«компоненты» можно прямо считать понятие «объекта ООП».
Тем не менее, всё, сказанное выше, касалось лишь логического
компонентного разбиения. Его отличительными признаками были – стремление
явно провести границу, обозначить процедуру, структуру, объект, которые
могли бы рассматриваться как «отдельное целое» и строить конструкцию
программы как взаимодействие всех этих «отдельных целых». При этом
«отдельное целое» понималось семантически уникально – «типовую
конструкцию», «стандартную процедуру» обычно встраивали в язык, так что
надобности в «изобретении кирпича» не возникало. Но, одновременно, в языке
была возможность неоднократного повторного использования удачных
«конструкций пользователя» – такая форма экзистенции программ, как
«библиотека процедур» или «библиотека классов» давно уже была понятной и
стандартной.
Проблемы подхода стали обнаруживаться со стороны реализации.
Вначале они были мелкими, например, при сохранении своего заголовка
(способа вызова и семантики параметров) алгоритм самой процедуры мог
усовершенствоваться. И новые версии «библиотеки процедур» шли с новым
алгоритмом, который уже существовавшие программы использовать не могли –
требовалась перекомпиляция или перелинковка всего проекта, дававшая совсем
другой модуль со всеми вытекающими отсюда проблемами обновления,
распространения и т.д. Потом размеры библиотек и модулей как-то незаметно
стали расти, так что если программа и использовала только небольшой
фрагмент библиотечного кода, всё равно приходилось «поднимать» очень
большой модуль всей библиотеки, который просто тупо занимал ресурсы
машины.
В ответ на это стала использоваться технология динамической
компоновки модулей, наиболее известная в своем проявлении «DLL» -
библиотеке динамической загрузки. Идея была простой – собирать программу в
единое адресное пространство только после её фактической загрузки на
исполнение. А до того весь программный комплекс существовал в виде
отдельных модулей-файлов, которые совершенно традиционно можно было
менять, переписывать и т.д. Да и загружать их на исполнение можно было уже
не все сразу, а по мере надобности. Словом, с точки зрения эффективности
использования ресурсов это был большой шаг вперед. А DLL с её свойствами
можно с полным основанием наименовать прямым предком «компоненты».
Только вот общая парадигма программного изделия
(исполняемого модуля) оставалась всё той же. Были примитивные типы,
процедуры, структуры, классы, которые «были отдельно», но компилировались
в общей среде и давали после своей компиляции абсолютно плоскую
конструкцию «всего вместе» нимало не напоминающую то, из чего они
делались. Как следствие, например, программист логически мог сделать
«развесистую классовую структуру», которая выдающимся образом описывала
нормальное функционирование модели, но катастрофически вся и обрушивалась,
если в процессе исполнения программы где-то обнаруживалась ошибка
исполнения. Стройность логического проектирования исподволь стала
приходить в противоречие с обобщённой надежностью программного изделия.
Дело также несколько усугубила такая особенность объектно-ориентированных
языков программирования, как «скрытый код» - откомпилированные модули, при
легкой ажурности их исходного текста, могли принимать угрожающие размеры.
И на вход компилятору всё равно надо было подавать всю «классовую
структуру» полностью… Т.е. «библиотечная проблема» начинала
воспроизводиться и на логическом уровне.
Одновременно появились трудности более высокого порядка.
Даже в самом прогрессивном для своего времени, объектно-ориентированном
подходе однозначно можно было смоделировать только сравнительно несложные
явления. Сложные же явления допускали несколько логически равнозначных, но
в инженерном смысле совсем не одинаково эффективных моделей. И чем сложнее
было явление, тем труднее было построить для него эффективную модель.
Поэтому в этих условиях очень естественной оказалась
конвергенция понятия «объект» и «модуль динамической компоновки», т.е.
такая конструкция программного комплекса, которая «объекты» делала
«самостоятельными DLL». Их уже не нужно было подавать на вход компилятора,
они были готовы к использованию, и их можно было активизировать из любой
исполняющейся программы по мере надобности. Но они не могли быть
«классическими DLL», они должны были быть «объектами» в смысле парадигмы
обращения с ними, возможности естественно описывать это взаимодействие в
языке, на котором ведется моделирование. И хотя это уже и есть настоящие
ортодоксальные «программные компоненты», хочется сказать ещё немного о
проблеме возможных определений...
|