лекция. Сборник лекций по МДК _Технология разработки программного обеспе. Курс лекций для специальности спо базовой подготовки
Скачать 4.41 Mb.
|
Модульное программирование Изначальной и, вероятно, все еще самой общей парадигмой програм- мирования является следующая: «Решите, какие процедуры вы желаете; ис- пользуйте лучшие из алгоритмов, которые можете найти». При этом внимание фокусируется на определении процедуры: выбор алгоритма, необходимого для выполнения желаемых вычислений. Процедурное программирование использует функции для создания по- рядка в лабиринте алгоритмов. Когда Вы изучали язык программирования Си, Вы фактически изучали процедурное программирование. Одной из важнейших составляющих процедурного программирования является модульное программирование. Главную идею модульного програм- мирования можно сформулировать следующим образом. При решении задач выполняются одни и те же действия. Поэтому разумно реализовать такие операции один раз в виде некоторых модулей и в дальнейшем иметь возможность присоединять эти модули к своей программе. Как правило, модуль представляет собой совокупность функций и дан- ных, используемых для решения задач определенного класса или реализующих набор операций над некоторым понятием. Например, в одном модуле могут быть определены функции, обеспечивающие ввод-вывод. В другом модуле мо- гут содержаться функции, позволяющие выполнять операции над строками. Третий модуль поддерживает выполнение операций над геометрическими объектами. А еще один, например, обеспечивает доступ к информации в неко- торой базе данных. Если при разработке программы мы обнаружим, что нам необходимо вы- полнять операции над строками и работать с геометрическими объектами, то нет необходимости реализовывать эти подпрограммы заново. Достаточно под- ключить соответствующие модули к нашей программе. Кроме этого необходи- мо знать, а каким образом надо обращаться к этим модулям, чтобы решить наши задачи. Каждый модуль, входящий в состав программы, решает задачи из своей области. Поэтому модули могут и должны разрабатываться отдельно друг от друга. Это позволяет вести коллективную разработку программной системы. Отдельные программисты или небольшие коллективы независимо от других разрабатывают свою часть программной системы. Кроме того, такой подход обеспечивает возможность модульной отладки, при которой отлаживается не вся программная система в целом, а отдельные ее компоненты. Такая техноло- гия обеспечивает быструю локализацию и устранение ошибок, что приводит к ускорению разработки программного продукта. Основным стержнем, на котором держится модульное программирова- ние, является аппарат подпрограмм (функций). Внутри модулей действия реа- 4лизуются посредством определения соответствующих подпрограмм. В вызывающей программе содержатся фактически только вызовы необходимых подпрограмм. Программные средства поддержки этой технологии наиболее полно впервые были реализованы в языке программирования Фортран и потом в подавляющем большинстве последующих языков. Для того чтобы воспользоваться средствами, предоставляемыми модулем, мы должны иметь некую дополнительную информацию по этому модулю. Во- первых, мы должны знать, какие подпрограммы какие действия выполняют. Во- вторых, мы должны знать, какую информацию мы должны передать этим подпрограммам для решения задачи, и каким образом она должна быть передана. Таким образом, важным моментом при использовании модульного программирования является обеспечение взаимодействия между модулями, или как говорят, определение интерфейса. Описание интерфейса, как правило, приводят в специальном заголовочном файле. Передача информации между подпрограммами (функциями) модулей в большинстве случаев осуществляется через список параметров при вызове соответствующей подпрограммы. Иногда допускается использование глобальных переменных, но каждый такой случай должен быть обоснован. Для рассматриваемой технологии характерна еще одна задача, а именно задача сборки программы из модулей. Существует несколько способов её ре- шения, некоторые из которых рассматриваются далее. Наиболее распространённым способом сборки программы является сбор- ка на уровне объектных файлов. Суть его состоит в том, что для каждого раз- работанного модуля, посредством компилятора с языка, отдельно получают объектный файл, а затем, используя компоновщик, объединяют их в единый исполняемый образ программы. Такие известные языки программирования как язык ассемблера, Си, Фортран поддерживают данную технологию. Однако, например, не менее известный язык Паскаль в его классическом варианте такой возможности не предусматривает. На рис.1 приведена иллюстрация рассмот- ренного метода. В современных вычислительных системах, поддерживающих многоза- дачность, широкое распространение получила новая концепция динамической 5 Рис. 1. Сборка на уровне объектных файлов module1.c module2.c moduleN.c . . . module1.o module2.o moduleN.o . . . programсборки программы. В её основе лежит использование разделяемых библио- тек (so) или библиотек динамического связывания (dll). Механизм динами- ческого связывания позволяет программе вызвать функцию, которая не является частью этой программы. Исполнимый код такой функции находится как раз в разделяемой библиотеке, которая компилируется и хранится отдельно от про- грамм, использующих ее. Различают два способа динамического связывания: связывание времени загрузки (load-time linking) и связывание времени исполнения (run-time linking). Первый случай имеет место, когда в программе производится явное обращение к функции из внешней библиотеки. В этом случае при создании программы используется специальная библиотека импорта (import library). Та- кая библиотека содержит информацию о функциях самой библиотеки, но не содержит исполнимого кода этих функций. Во время загрузки программы, опе- рационная система использует информацию из библиотеки импорта, чтобы определить расположение исполнимого кода разделяемой библиотеки и загру- зить его в память компьютера. Связывание времени исполнения используется в том случае, когда про- грамме во время своего выполнения может потребоваться получить доступ к библиотекам, неизвестным на этапе разработки программы. При этом сама программа может управлять процессом загрузки и выгрузки таких библиотек, а обращение к их функциям производится неявным способом, используя специ- альные точки входа. Такой механизм обусловлен тем, что на этапе разработки программы имя вызываемой функции может быть неизвестно, а, следователь- но, Вы не можете произвести к ней явное обращение вида myFunction(); а должны использовать конструкцию вида: void* handle = dlopen("libm.so", RTLD_LAZY); void (*myFunction)(); //myFunction — указатель на функцию *(void **) (&myFunction) = dlsym(handle, "myFunction"); (*myFunction)(); 1.2. Характеристики модулей Важнейшим принципом разработки модуля является принцип информа- ционной закрытости. Этот принцип утверждает, что содержание модулей должно быть скрыто друг от друга. Модуль должен определяться и проектиро- ваться так, чтобы его содержимое (функции и данные) было недоступно тем модулям, которые не нуждаются в такой информации (клиентам). Этот принцип иллюстрируется на рис.2. Информационная закрытость означает, что все модули независимы и об- мениваются только необходимой для работы информацией. Кроме этого доступ к операциям и структурам данных модуля извне его ограничен. Следование принципу информационной закрытости даёт определённые 6преимущества. С одной стороны обеспечивается возможность разработки мо- дулей независимыми разработчиками, что уменьшает время разработки про- граммной системы. Но он даёт преимущества и в малых проектах, когда коли- чество разработчиков невелико, или он вообще один. Это преимущество за- ключается в лёгкой модификации системы. При использовании принципа веро- ятность распространения ошибок сильно уменьшается, так как многие данные и функции локализованы внутри модуля и скрыты от других частей системы. В этом случае модуль играет роль «чёрного ящика». Его содержимое (реализация) недоступно другим модулям, а управление им (интерфейс) про- стое. Изменение реализации модуля никоим образом не может повлиять на его пользователей. Для оценки уровня информационной закрытости модуля можно исполь- зовать его внутреннюю характеристику - связность. Под связностью модуля понимается мера зависимости его частей. Чем выше связность модуля, тем луч- ше результат проектирования. Далее приведены различные типы связности в порядке её уменьшения. При этом первые три свидетельствуют о хорошем ка- честве модуля, а остальные о недостаточно высоком качестве разработки. ●Функциональная связность. Части модуля вместе реализуют одну проблемную задачу (операцию). Например: вычисление синуса угла, проверка орфографии, вычисление зарплаты сотрудника, определение места пассажира. Модуль выполняет только то, для чего он предназначен. Поэтому модуль вы- числения синуса не должен печатать его значение. ●Информационная связность. Выходные данные одной части использу- ются как входные данные в другой части модуля. Порядок выполнения дей- ствий строго определён и подобен конвейеру. ●Коммуникативная связность. В этом случае части модуля связаны по данным, то есть они работают с одним и тем же типом структуры данных. При этом порядок выполнения действий безразличен. ●Процедурная связность. Этот тип связности соответствует случаю, когда 7 Рис. 2. Информационная закрытость модуля Интерфейс Реализация модули-клиенты Модульчасти модуля связаны порядком выполняемых ими действий, реализующих некоторый сценарий поведения. При этом зависимость по данным между частями модуля отсутствует. ●Временная связность. Различные части модуля не связаны между собой ни по данным, ни по порядку выполнения, но необходимы в один и тот же пе- риод работы системы. ●Логическая связность. В этом случае части модуля объединены по принципу функционального подобия. Например, модуль включает в себя раз- личные функции обработки ошибок. К серьёзным недостаткам таких модулей относится сложное сопряжение с другими модулями и большая вероятность внесения ошибок при необходимости изменений. ●Связность по совпадению. В модуле отсутствуют явно выраженные внутренние связи вообще. Для оценки меры взаимозависимости модулей по данным применяется другая характеристика – сцепление. Сцепление является внешней характери- стикой модуля, которую желательно уменьшать. Выделяют несколько типов сцепления, которые перечислены ниже в порядке его увеличения. ●Сцепление по данным. В этом случае функции модуля А вызывают функции модуля B, причём все входные и выходные параметры вызываемого модуля являются простыми элементами данных. ●Сцепление по образцу. Здесь в качестве параметров используются структуры данных. ●Сцепление по управлению. Один модуль явно управляет функциониро- ванием другого модуля с помощью флагов или переключателей, посылая ему управляющие данные. ●Сцепление по внешним ссылкам. В этом случае оба модуля ссылаются на один и тот же глобальный элемент данных. ●Сцепление по общей области. Здесь модули разделяют одну и ту же глобальную структуру данных. ●Сцепление по содержанию. Один модуль прямо ссылается на содержа- ние другого модуля (в обход интерфейсной части вызываемого модуля). Соглашения по разработке модулей За время существования такой дисциплины как программирование, в ней были выработаны определенные правила оформления программных модулей. Подавляющее большинство разработчиков программного обеспечения стре- мятся их соблюдать. Следование этим правилам позволит получить хорошо структурированную, легко читаемую программу. Использование одних и тех же правил оформления модулей позволяет легче и быстрее разобраться в них, значительно уменьшает количество «необязательных» ошибок. Те же, кто эти правила совершенно игнорируют, всех этих преимуществ лишены. На этапе обучения следование приведённым далее правилам обязательно. Вместе с тем нельзя рассматривать их как некие догмы. Это всего лишь рекомендации. В процессе трудовой деятельности вы вынуждены будете их несколько адаптиро- 8вать в соответствии с требованиями трудовой организации, компании. 1. Проектируйте модуль со связностью не ниже коммуникативной. Он не должен быть просто совокупностью никак не связанных между собой функций и данных. 2. Модуль должен охватывать, как можно большую часть задач рассматри- ваемой области, и в то же время набор доступных подпрограмм следует сделать минимальным и простым в использовании. 3. Добейтесь, чтобы сцепление модуля было по данным или по образцу. В противном случае изменение некоторого модуля может вызвать лавинообразные изменения в других модулях. 4. При разработке модуля с именем Name, как правило, создается два файла: файл-заголовок Name.h и файл-реализация Name.cpp. В файле-за- головке определяется интерфейс к данному модулю. Он включает в себя опре- деление констант, новых типов данных, описаний функций с обязательным указанием их параметров и описаний глобальных переменных, определенных в модуле и доступных для других частей программы. Если модуль «A» обраща- ется к модулю «B», то в модуле «A» должен быть подключен файл-заголовок модуля «B». Поскольку файл-заголовок может подключаться несколько раз, то во избежание дублирования используют конструкцию следующего вида #ifndef name_h #define name_h //... //описания //... #endif Файл-реализация Name.cpp содержит определения глобальных и стати- ческих переменных модуля, определение новых типов данных, используемых только внутри этого модуля и реализации функций. Очевидно, что в этом мо- дуле должно производиться подключение файла-заголовка. 5. Минимизируйте использование глобальных переменных. Наилуч- шим решением является отказ от их использования. Поскольку глобальные переменные могут быть сделаны доступными из любого модуля, то чрезвычай- но трудно контролировать их использование. Настоятельно рекомендуется передавать в функцию все необходимые ей данные в качестве параметров. 6. Избегайте слишком больших реализаций функций. Например, можно потребовать, чтобы вся реализация функции умещалась в определённое коли- чество строк (количество строк на экране монитора, в пределах одного листа при печати и т .д.). Кроме этого важно избегать глубоких вложений внутри функции (не более 2 вложенных операторов). Игнорирование этой рекоменда- ции приводит к более трудному восприятию программного текста, реализую- щего данную функцию, а что еще более важно - к повышению вероятности со- вершить ошибку при кодировании алгоритма. 7. Каждая строка программы должна содержать не более одного оператора. Также избегайте объявления нескольких переменных на одной строке. 98. Программный код пишется для чтения не компилятором, а человеком. Программный код должен сам себя документировать и средства языка C позво- ляют обеспечить выполнение этого требования. Как минимум, все используе- мые в программах имена должны быть содержательными. Понять смысл оператора digitCount = digitCount + 2 гораздо проще, чем c = c+2. 9. Хорошая практика программирования состоит в том, чтобы всем явным константам давать символические имена и их использовать в программе. Указанное правило не относится к константам, которые даже в принципе не могут поменяться. Например, индексация элементов в векторе начинается с 0, поэтому определение для этой константы символического имени бессмысленно и только запутывает программный код. 10. Используйте отступы для выделения операторов и их блоков внутри условного оператора, оператора цикла и выбора. Следование этой реко- мендации позволяет избежать ошибок, связанных, например, с расстановкой скобок { и }. Но еще более важно, что при таком подходе четко прослеживается структура программы. 1.4. Пример: операции над полиномами В качестве примера, иллюстрирующего «хороший стиль» программиро- вания, рассмотрим следующую задачу. Пусть требуется реализовать набор функций для выполнения операций над полиномами переменной степени Pn x=an x nan−1 x n−1a1 xa0 . В рамках нашего примера ограничимся операциями ввода полинома со стандартного устройства ввода и вывода на стандартное устройство вывода. Для иллюстрации использования реализован- ных операций рассмотрим тестовую программу циклического ввода-вывода полинома. Примем, что необходимость повторного ввода полинома определя- ется по команде пользователя. Если пользователь в ответ на запрос о продол- жении работы с программой ввел y, то необходимо продолжить, в противном случае программа завершается. Из определения понятия «полином» следует, что он обладает следующи- ми характеристиками: • степень полинома; • набор коэффициентов полинома. Для представления понятия полинома определим тип данных Polinom, представляющий собой структуру с указанными выше полями. С целью упро- щения примера введем дополнительное ограничение: максимально возможную степень полинома n max . Конечная программа будет состоять из трех файлов. В файле polinom.h содержится определение структуры описания полинома, в файле polinom.cpp содержатся реализации функций над полиномом, а в файле main.cpp приводит- ся текст основной программы. //polinom.h 10//файл содержит определение структуры данных //для описания полинома #ifndef POLINOM_H #define POLINOM_H //максимальная степень полинома const int PolinomMaxDegree = 10; //структура описания полинома struct Polinom { int n; //степень полинома double a[PolinomMaxDegree+1]; //коэффициенты полинома }; extern int PolinomRead(Polinom *p); ///функция ввода полинома extern void PolinomWrite(Polinom *p); ///функция вывода полинома #endif //polinom.cpp //файл содержит реализации функций над полиномом #include "polinom.h" int ReadPolinom(Polinom *p) { int n; printf("Введите степень полинома:"); scanf("%d",&n); if( nPolinomMaxDegree ) return -1; printf("Введите коэффициенты полинома:"); for( int i = n; i >= 0; --i ) scanf("%lf",&p->a[i]); //старший коэффициент должен быть !=0 if( p->a[n]==0 ) return -1; p->n=n; return 0; } void WritePolinom(Polinom *p) { printf("Коэффициенты полинома: "); for( int i = p->n; i >= 0; --i) 11printf("%lg ",p->a[i]); printf("\n"); return; } //main.cpp - Программа тестирования ввода-вывода полиномов. //Версия 1.0 //Разработчик: Иванов И.И. //Дата разработки: 25 декабря 2000 г. #include #include "polinom.h" extern int NeedContinue(); int main() { char appName[] ="Программа ввода-вывода полиномов. Версия 1.0\n"; Polinom pol; //исходный полином puts(appName); //напечатать назначение программы do { if( ReadPolinom(&pol) != -1 ) WritePolinom(&pol); else printf("Некорректно заданы значения параметров\n"); } while( NeedContinue() ); return 0; } int NeedContinue() { printf("продолжать?(y/n):"); return getchar() == 'y'; } 1.5. Технология проектирования сверху-вниз 1.5.1.Постановка задачи При создании сложных программных систем важной задачей является правильная организация процесса проектирования системы. Одним из самых распространенных методов решения этой задачи является технология проек- тирования «сверху вниз». Этот метод напрямую связан с технологией мо- дульного программирования. Проектирование и реализация программной системы возникают не сами 12по себе, а как следствие постановки некоторой задачи, которую требуется ре- шить. При этом, формулируя задачу, мы имеем дело с понятиями реального мира, а не с абстрактными числами, строками, функциями и т. д. Например, в задаче рисования мы имеем дело с понятиями «картинка», «инструмент рисо- вания» и т. д. Над объектами, представляющими понятие, можно совершать различные действия, либо они сами могут выполнять какие-либо операции. Например, по- лученную картинку можно сохранить для дальнейшего использования, открыть ее для просмотра, вставить в документ, редактировать ее, вставляя и удаляя геометрические фигуры и т. д. Во многих случаях понятие может быть определено, используя ряд дру- гих, более простых понятий. Возвращаясь к нашему примеру, «картинку» мож- но определить как совокупность различных геометрических фигур, таких как точки, линии, окружности. В этом случае понятие «геометрическая фигура» выступает как понятие более низкого уровня, и используется для определения «картинки». В свою очередь понятие геометрической фигуры можно также де- тализировать используя перечисленные выше понятия. Объект вместе с операциями, связанными с ним, удобно считать неким исполнителем с определенной системой предписаний (действий). При этом исполнителя можно рассматривать с внешней и внутренней точек зрения. Вы- полнение любой программы состоит в проведении каких-то действий над каки- ми-то объектами, то есть в использовании исполнителей. При использовании исполнителя интересны его внешние свойства, а именно, какие у него есть предписания, что получается в результате их выполнения и т. д. Описание этих свойств называется внешним описанием исполнителя. Оно позволяет им поль- зоваться, но ничего не говорит о его внутреннем устройстве, о его реализации. Но для того чтобы исполнителя можно было использовать, необходимо иметь его реализацию, причем возможно на базе других исполнителей. Таким образом, при реализации мы рассматриваем исполнителя с внутренней точки зрения. На этом этапе нам интересна его структура и как он выполняет те или иные действия. При этом реализация каждого из исполнителей может рассмат- риваться как отдельная подзадача в рамках исходной задачи. При проектировании программной системы у разработчика имеется в распоряжении ряд исполнителей, уже реализованных ранее, либо под- держиваемых самой системой программирования. Такие исполнители назовем базовыми исполнителями. Например, базовыми исполнителями можно считать библиотеку файлового ввода-вывода, математическую библиотеку, по- нятие массива с определенными над ним операциями, либо некоего исполните- ля, реализованного Вами ранее. Таким образом, задачу разработки программ- ной системы можно определить как задачу реализации искомого исполнителя A на основе совокупности исполнителей E1 ,, Ek . Реализацию A на базе указанной совокупности исполнителей будем обозначать как RA , E1 ,, Ek . 131.5.2.Декомпозиция задачи Как мы с Вами установили, исходную задачу удобно решать, разбивая ее на подзадачи с помощью промежуточных исполнителей. Технология про- граммирования «сверху вниз» предлагает такое разбиение вести от исполнителя A (сверху) к базовым исполнителям E1 , E2 ,, Ek (вниз). В соответствии с указанным подходом сначала выделяется и реализуется общая идея алгоритма, и только потом прорисовываются его детали. При этом каждый раз, делая очередной шаг вниз, формулируются уточнения. Схематиче- ски результаты проведения этого процесса можно представить следующим об- разом: Таким образом, модульная структура программы имеет вид графа (в при- веденном примере - древовидную структуру). В узлах такого дерева размеща- ются программные модули, а направленные дуги (стрелки) показывают стати- ческую подчиненность модулей, т. е. каждая дуга показывает, что в тексте мо- дуля, из которого она исходит, есть ссылка на модуль, в который она входит. Процесс разбиения задачи на ряд подзадач носит название декомпози- ции. Конечная программа состоит из нескольких слоев и представляет собой иерархию исполнителей. При этом все или некоторые из них могут быть уже реализованы ранее, а, следовательно, достаточно их подключения к основной программе. Остальные же исполнители необходимо реализовывать, при этом они, в свою очередь, могут быть реализованы с привлечением других исполни- телей. Проводя декомпозицию задачи, необходимо руководствоваться скорее тем, что надо сделать, нежели тем, на какой базе. То есть, при использовании технологии проектирования «сверху-вниз», Вы создаете реализацию некоторо- го исполнителя в удобном, естественном виде, не привязываясь жестко к уже существующим исполнителям. Технологическая цепочка проектирования «сверху-вниз» приведена на рис.4. Центральным моментом технологии «сверху-вниз» является шаг де- композиции. Эта операция состоит в том, чтобы по имеющемуся внешнему описанию исполнителя A получить его реализацию на базе придуманного нами исполнителя B и одновременно внешнее описание придуманного испол- нителя B . 14 Рис. 3. Результат проведения декомпозиции A B1 E3 B2 E1 C1 E2 C2 E6 E4 E5Шаг декомпозиции можно провести различными способами. Придумывая нового исполнителя B , следует учитывать несколько требований. С одной стороны, необходимо как можно большую часть работы переложить на нового исполнителя B . Вместе с тем проведенный шаг декомпозиции должен прибли- жать нас к базовым исполнителям E . И, конечно же, придуманный исполни- тель должен соответствовать некоему реальному понятию. Обычно, при проведении декомпозиции, исполнители реализуются на базе нескольких исполнителей, а не одного. В этом случае в результате очеред- ного шага появляются внешние описания этих исполнителей и одна реализация R A , B1 ,, Bk . Некоторые из этих исполнителей могут быть базовыми, то есть Bi ∈{E1 ,, Ek } , либо получены ранее, на предыдущих шагах. Очевидно, что если все исполнители удовлетворяют этим условиям, то процесс проведе- ния декомпозиции можно считать завершенным. При проведении декомпозиции мы можем увидеть, что придуманная нами иерархия промежуточных исполнителей неудачна, что какой-то исполни- тель не реализуется или реализуется очень сложно, что внешнее описание ис- полнителя необходимо уточнить или изменить. В этом случае приходится воз- вращаться на несколько шагов назад и начинать сначала. Таким образом, проектирование и программирование являются итеративными (повторяющимися) процессами. Среди всего прочего проектирование «сверху-вниз» обладает существен- ным преимуществом. При его использовании удается совместить процесс от- ладки с процессом разработки. Пусть, например, получена реализация A , а реализации некоторых из промежуточных исполнителей отсутствуют. В этом случае нереализованные части программы заменяются имитаторами (заглуш- ками). Каждый имитатор модуля представляется достаточно простым про- граммным фрагментом, который, в основном, сигнализирует о самом факте об- ращения к имитируемому модулю, производит необходимую для правильной работы программы обработку значений его входных параметров (иногда с их распечаткой) и выдает, если это необходимо, заранее подготовленный подходя- щий результат. После завершения тестирования и отладки реализации исполнителя A производится переход к реализации и последующему тестированию одного из исполнителей, которые в данный момент представлены имитаторами, если та- ковые имеются. Для этого имитатор выбранного для тестирования исполнителя заменяется его реализацией и, кроме того, добавляются имитаторы тех испол- 15 Рис. 4. Технологическая цепочка процесса декомпозиции А B R(А,B) Шаг декомпозиции E R(Z,E) . . .нителей, к которым может обращаться выбранный для тестирования исполни- тель. Кроме того, очень удобно реализацию каждого исполнителя производить в своем модуле, а его внешнее описание в соответствующем этому модулю за- головочном файле. Если исполнители A и B используют исполнителя C в качестве промежуточного, то доступ A и B к внешнему описанию C можно обеспечить простым подключением файла-заголовка. Хранение внешних описаний всех исполнителей в одном файле-заголовке может показаться рациональным. Однако такой подход неприемлем хотя бы в силу того, что исполнители уже фактически не отделены друг от друга. При разработке другой программной системы мы можем пожелать воспользоваться некоторыми из реализованных ранее исполнителей. Однако поскольку их внешние описания объединены, то мы вынуждены будем использовать все, а не только нужные нам. Кроме того, при изменении внешнего описания какого- либо исполнителя, повторную компиляцию придется провести для всех ис- полнителей. В случае же отдельных файлов-заголовков компиляция потребуется только для тех реализаций, которые имеют дело с изменившимся исполнителем. Этот фактор особенно важен при разработке больших программных систем. Как Вы могли обратить внимание, рассмотренная схема не предполагает обязательного проведения всей декомпозиции программной системы на этапе проектирования до начала программирования. Представляется сомнительным, чтобы до программирования модулей можно было разработать структуру всей программы точно и содержательно. Наоборот, часто более надежным методом оказывается одновременное проведение декомпозиции и получение реализаций исполнителей. |