Современные технологии программирования |
Конспект лекций |
Лекция 8. Модульное программирование
Модуль
Модульное программирование
Иерархия модулей
Пример разбиения задачи на модули
Разделы модуля Delphi и их назначение
Области видимости в модуле
Модуль: логический, уровень языка программирования (unit), физический (организация модуля в памяти)
Раздел модуля
Раздел интерфейса
Раздел uses
Раздел реализации
Раздел инициализации
Раздел финализации
Раздел заголовка
На каждом уровне развития информационных технологий и общества возникают задачи, которые не могут быть решены в условиях заданных ограничений (время) одним исполнителем. Выходом из этой ситуации является разбиение задачи на уровне проекта на отдельные части – логические модули и дальнейшей разработкой каждой из частей отдельным исполнителем. Причём разбиение задачи на логические модули необходимо выполнить так, чтобы эти части были бы логически не зависимы. Это позволит выполнить их независимую разработку. Разбиение задачи на части должно предполагать дальнейшее объединение их в целое для решения исходной задачи. Для того чтобы задачу можно было реализовать в виде отдельных логически не зависимых частей, в составе языка должно быть средство, обеспечивающее поддержку модульного проекта.
Общие принципы проектирования систем
Как было отмечено ранее, одной из основных проблем, которые приходится решать при создании больших и сложных систем любой природы, в том числе и ПО, является проблема сложности. Ни один разработчик не в состоянии выйти за пределы человеческих возможностей и понять всю систему в целом. Единственный эффективный подход к решению этой проблемы, который выработало человечество за всю свою историю, заключается в построении сложной системы из небольшого количества крупных частей, каждая из которых, в свою очередь, строится из частей меньшего размера, и т.д., до тех пор, пока самые небольшие части можно будет строить из имеющегося материала. Этот подход известен под самыми разными названиями, среди них такие, как “разделяй и властвуй” (divide et imperd), иерархическая декомпозиция и др. По отношению к проектированию сложной программной системы это означает, что ее необходимо разделить (декомпозировать) на небольшие подсистемы, каждую из которых можно разрабатывать независимо от других. Это позволяет при разработке подсистемы любого уровня иметь дело только с ней, а не со всеми остальными частями системы. Правильная декомпозиция является главным способом преодоления сложности разработки больших систем ПО. Понятие “правильная” по отношению к декомпозиции означает следующее:
Более подробно эти принципы будут рассмотрены в рамках объектно-ориентированного анализа.
Структура системы должна быть такой, чтобы все взаимодействия между ее подсистемами укладывались в ограниченные, стандартные рамки, т.е.:
Инкапсуляция (принцип “черного ящика”) позволяет рассматривать структуру каждой подсистемы независимо от других подсистем. Интерфейсы позволяют строить систему более высокого уровня, рассматривая каждую подсистему как единое целое и игнорируя ее внутреннее устройство.
Существуют два основных подхода к декомпозиции систем. Первый подход называют функционально-модульным, он является частью более общего структурного подхода. В его основу положен принцип функциональной декомпозиции, при которой структура системы описывается в терминах иерархии ее функций и передачи информации между отдельными функциональными элементами. Второй, объектно-ориентированный подход, использует объектную декомпозицию. При этом структура системы описывается в терминах объектов и связей между ними, а поведение системы описывается в терминах обмена сообщениями между объектами.
Нововведения в конструкции “модуль” и её использование
С выходом пакетов Delphi и Delphi 2.0 изменилась технология разработки приложений, в результате чего существенно возросло значение языковой конструкции "модуль" (unit), в синтаксисе которой были сделаны некоторые изменения. Все это выразилось в следующем: в среде Delphi каждой форме обязательно соответствует свой модуль и не визуальные алгоритмические действия так же, как правило, оформляются в виде отдельных модулей. Таким образом, среда Delphi имеет встроенную поддержку концепции модульного программирования на языке Object Pascal, что стимулирует прогрессивный и надежный стиль программирования с широким использованием модулей, и тем самым выгодно отличает Delphi и Object Pascal от других современных средств разработки приложений;
Модули и модульное программирование
Основным принципом модульного программирования является принцип “разделяй и властвуй”. Модульное программирование – это организация программы как совокупности небольших независимых блоков, называемых модулями, структура и поведение которых подчиняется определённым правилам.
Заметим, что необходимо различать слово “модуль”, в смысле синтаксической конструкции unit Object Pascal (уровень языка программирования), от слова “модуль” в смысле единицы дробления программы на части в процессе проектирования программы (логический уровень), которые могут быть реализованы в виде подпрограмм.
Использование модульного программирования позволяет упростить тестирование программы и обнаружение ошибок. Аппаратно-зависимые подзадачи могут быть строго отделены от других подзадач, что улучшает мобильность создаваемых программ.
Упрощается процесс повышения эффективности программ, так как критичные по времени модули могут многократно переделываться независимо от других. Кроме того, модульные программы значительно легче понимать, а модули могут быть использованы как строительные блоки в других программах.
Количество модулей в проекте должно определяться декомпозицией поставленной задачи на независимые подзадачи. В предельном случае модуль может использоваться даже для заключения в него всего лишь одной процедуры, если необходимо, чтобы выполняемое ею локальное действие было гарантировано независимо от влияния других частей программы при любых изменениях в коде проекта. В частности, такое использование модуля характерно для класса задач реального времени, в которых критерий надёжности и предсказуемости поведения программы является ключевым.
Рассмотрим ещё один теоретический вопрос, связанный с модульным программированием. Это касается формы модульного проекта.
Придание иерархической структуре модульного проекта хорошей формы позволяет упростить процесс её разработки. Число модулей, которые подключаются каким-либо модулем, и число модулей, которые его подключают, оказывают влияние на сложность проекта. Йодан назвал число модулей, подключаемых из данного модуля, размахом или шириной управления модулями. Наряду с большим размером модуля, очень маленькая или очень большая ширина управления является признаком плохой схемы разбиения на модули. В общем случае ширина управления не должна превышать 10-ти.
Ниже приведён пример описания проекта программы с помощью схемы иерархии логических модулей. Программа предназначена для преобразования р-ичного числа представленного строкой в числовой формат. Схема иерархии логических модулей отображает связь модулей по управлению, т.е. показывает с помощью линий передачи управления, какие модули может вызвать данный модуль. Модуль может вызвать только модули нижележащего уровня непосредственно с ним связанные.
Пример
На рисунке прямоугольниками обозначены логические модули, линиями обозначены связь между модулями по управлению. Каждый модуль управляет вызовом модулей нижележащих уровней непосредственно связанных с ним.
На уровне языка программирования каждый логический модуль может быть реализован в форме подпрограммы (процедуры или функции) или модуля (unit). Выбор формы реализации модуля на языке программирования зависит от размеров логического модуля.
Пример
unit InnerFm;
interface
function TypeByBit(var b; n: integer): AnsiString;
{ Заголовок функции, которая возвращает строку, содержащую побитное представление переменной b. Эта функция предназначена для общего пользования другими модулями и программами }
implementation
function BytebyBit(b: byte): string;
{Функция, скрытая от пользователей модуля. Она предназначена для внутреннего использования функцией TypeByBit. Функция возвращает строку, содержащую побитное представление байта b}
var
i: byte;
begin
Result:= '';
for i:=0 to 7 do begin
if (b and (1 shl i))= 0 then Result:= '0'+ Result else Result:= '1'+ Result;
end;
end;
function TypeByBit;
{Полное описание общедоступной функции, заголовок которой представлен в разделе интерфейса}
Type
t = array[1..Maxint] of byte;
var
i: byte;
begin
Result:= '';
for i:=1 to n do
Result:= ByteByBit(t(b)[i]) + Result;
end;
end.
Головная программа, использующая функцию TypeByBit из модуля UExmpl1
program PExmpl1;
{$APPTYPE CONSOLE}
uses
SysUtils,
UExmpl1 in 'UExmpl1.pas';
var
b: byte;
i: integer;
r: real;
f: text;
begin
{ TODO -oUser -cConsole Main : Insert code here }
AssignFile(f,'data.txt');
Rewrite(f);
//------------------------------------------------------------------------------
write('input byte: '); readln(b);
writeln(f,'input byte: ',b);
writeln('byte by bit: ',TypeByBit(b, sizeof(b)));
writeln(f,'byte by bit: ',TypeByBit(b, sizeof(b)));
//------------------------------------------------------------------------------
write('input integer: '); readln(i);
writeln(f,'input integer: ',i);
writeln('integer by bit: ',TypeByBit(i, sizeof(i)));
writeln(f,'integer by bit: ',TypeByBit(i, sizeof(i)));
//------------------------------------------------------------------------------
write('input real: '); readln(r);
writeln(f,'input real: ',r);
writeln('real by bit: ',TypeByBit(r, sizeof(r)));
writeln(f,'real by bit: ',TypeByBit(r, sizeof(r)));
readln;
CloseFile(f);
end.
Пример работы функции TypeByBit побитного вывода значения переменной из памяти
input byte: 6
byte by bit: 00000110
input integer: -6
integer by bit: 11111111111111111111111111111010
input real: 6.00000000000000E+0000
real by bit: 0100000000011000000000000000000000000000000000000000000000000000
Структура модуля и назначение его разделов
Модуль. Синтаксическая диаграмма
Рисунок. Структура модуля
uses {$U My} MyUnit;
Это означает, что модуль MyUnit необходимо искать в файле My.pas.
Синтаксическая диаграмма “Заголовок модуля”
Рисунок. Заголовок модуля
Раздел интерфейса
Синтаксис «интерфейс модуля»
Рисунок. Интерфейс модуля
Раздел реализации
Синтаксис «Раздел реализации»
Рисунок. Раздел реализации
Раздел инициализации
Синтаксис «Раздел инициализации»
program Project2;
{$APPTYPE CONSOLE}
uses
SysUtils,
Unit1 in 'Unit1.pas';
begin
writeln(s);//выводится значение 'begining value'
readln;
end.
unit Unit1;
interface
var
s: String;//
implementation
initialization
s:= 'begining value';//переменная s инициализируется значением
end.
Раздел финализации
USES Mymod;
в программе Test вы получили доступ к ресурсам, описанным в интерфейсном разделе модуля Mymod.
Пример. Использование модулей
program ExmplP3;
uses ExmplUn3;
{ ресурсы интерфейсного раздела модуля ExmplUn3 доступны программе }
begin
writeln(min(a,3));{ вызывается функция из модуля ExmplUn3 }
writeln(E); { ссылка на константу из модуля ExmplUn3 }
Readln;
end.
unit ExmplUn3;
interface
type
Complex = record
re, im :Real
end;
const
E = 2.711828;
function min(a,b:real):real;
var
a: real = 10;
implementation
function min;
{ ВОЗВРАЩАЕТ МИНИМАЛЬНЫЙ ИЗ A,B.}
begin
if a>b then min:=b
else min:=a;
end;
end.
Рисунок. Область видимости идентификаторов
Пример. Область видимости идентификаторов
program ExmplP4;
{$APPTYPE CONSOLE}
uses A,B;
var
c: integer;
d: A.E;{Ссылка на тип E модуля A}
E: Real;
begin
c:= 25;
writeln(c);{Ссылка на переменную c программы}
writeln(a.c);{Ссылка на переменную c программы модуля A}
writeln(b.c);{Ссылка на переменную c программы модуля B}
writeln(b.E);{Ссылка на функцию E c модуля b}
writeln(s);{Ссылка на строку s модуля b}
writeln(a.s);{Ссылка на строку s модуля a}
Readln;
end.
unit A;
interface
type
E = record
re,im:real
end;
const
c = 100;
s = 'строка модуль A';
implementation
end.
unit B;
interface
const
c = 45;
s = 'строка модуль B';
function E: Real;
implementation
function E;
begin
E:= 2.711828;
end;
end.
Компиляция модулей
Project|Compile All Projects
Используйте команду компилировать весь проект, чтобы откомпилировать все файлы, входящие в проект, в которые были внесены изменения с момента последнего построения исполняемого файла. Эта команда подобна команде Project|Build All Projects, за исключением того, что Project|Compile All Projects перекомпилирует только те файлы проекта, в которые были внесены изменения с момента последнего создания исполняемого файла, а команда Project|Build All Projects перекомпилирует все компоненты проекта.
Команда Project|Compile All Projects перекомпилирует изменённые файлы сверху донизу, как они перечислены в списке Project Manager.
Если вы выберите Show Compiler Progress со страницы Preferences диалогового окна команды меню Tools|Environment Options, в диалоговое окно компилятора будет отображаться информация о ходе компиляции и её результатах. Когда ваше приложение успешно откомпилируется, выберите команду OK, чтобы закрыть это окно.
Если компилятор обнаружит ошибку, Delphi отобразит сообщение об ошибках и разместит курсор в строке, содержащей ошибку.
Компилятор создаёт исполняемый файл в соответствии со следующими правилами
Проект (.dpr) файл всегда перекомпилируется.
Если в исходном файле модуля (unit) появились исправления с момента последней перекомпиляции, модуль перекомпилируется. Когда unit перекомпилирован, Delphi создаёт файл с расширением .dcu для этого unit.
Если Delphi не находит файла unit с исходным кодом, то файл не компилируется.
Если изменилась интерфейсная часть модуля, все зависящие от него модули также перекомпилируются.
Если к модулю подключены внешние подпрограммы (файлы с расширением .obj), и в них были внесены изменения, то модуль перекомпилируется.
Если модуль содержит include файлы, и в них были внесены изменения, то модуль перекомпилируется.
Project|Build All project
Команда построить проект строит проект заново (компилирует и собирает все компоненты проекта), независимо от того, были ли внесены в них изменения или нет. Эта команда полезна, когда вы внесли изменения в настройки или директивы компилятора и хотите быть уверены, что весь код откомпилирован в соответствии с ними.
Эта команда идентична команде Project|Compile за исключением того, что она перекомпилирует все компоненты проекта, в то время как Project|Compile перекомпилирует только те компоненты, в которые были внесены изменения после последнего построения проекта.
Рисунок. Компиляция модулей
Назначение
Структура
Заголовок
Интерфейс
Раздел реализации
Раздел инициализации
Использование
Область видимости идентификаторов
Компиляция модулей
Источники дополнительных сведений