Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
36
Добавлен:
23.03.2015
Размер:
933.38 Кб
Скачать

13.2.2. Разделы абстракций

Разобьем часть рабочего журнала, предназначенную для от­дельных абстракций, еще на пять частей: 1)спецификация функ­ционального поведения а-бстракции; 2)огчисание ограничений по эффективности, которые необходимо со^элтости; 3)идформация о способе реализации; 4)информация с» методах тестирования;5)дополетительная информация, не попадамощая в четыре перечис­ленные категории.

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

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

Ограничения по эффективности могут быть представлены множеством способов. Чаще всего они выряжаются в виде функций размера входных данных. Мы можем, на пример, написать в ка­честве части описания процедурыsortс формальным аргументом х типlist[int] следующее:

Эффективность

худшее время выполнения ==порядок (длина (х) *log(дли­на (х))

дополнительная оперативная память ==порядок (длина (х)) максимальный временный объем оперативной памяти ==по­рядок (длина (х))

Проектирование

Это означает

1.Время, необходимое для сортировки списка х никогда не будет больше некоторой постоянной константы, умноженной на произведение длины х и логарифма (по основанию 2)длины х.

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

3.В процессе выполнения процедурыsortобъем отведенной под задачу оперативной памяти не может превысить значения некоторой константы, умноженной на длину х.

Для большинства задач достаточно бывает указать относи­тельно простые ограничения по эффективности, подобные только что рассмотренным. Иногда, однако, полезно ограничить эти константы или даже ввести абсолютные ограничения. Абсолютное значение верхней границы по времени необходимо для задач, работающих в реальном масштабе времени. Аналогично мы на­кладываем ограничения на объем памяти под программы, выпол­няющиеся на небольших машинах или машинах, не имеющих виртуальной организации памяти. Если процедураsortдолжна быть реализована в системе реального времени, установленной на машине без внешней памяти, то мы могли бы записать]

Эффективность

худшее время выполнения == 1секунда

дополнительная оперативная память ==порядок (длина (х)) максимальный временный объем оперативной памяти •=•поря­док (длина (х))

дополнительная внешняя память ==О временная дополнительная внешняя память ==О (Для процедурыsortабсолютные ограничения по времени будут приемлемы только в том случае, если в спецификацииrequiresна длину входного списка х также накладываются ограничения.)

Раздел рабочего журнала, содержащий информацию о том, как должна быть реализована абстракция, должен содержать список вспомогательных абстракций. Для типов данных этот раздел должен описывать представление, инвариант представле­ния и функцию абстракции, связанную с предполагаемой реализа­цией. Данный раздел должен также включать описание принципа работы реализации. Это описание может быть опущено, если прин­цип работы реализации очевиден, однако при использовании слож­ной «хитроумной» реализации он необходим. Очень часто бывает полезно привести набросок используемого алгоритма, например:

Реализочать sort (х) при помощи рекурсивного алгоритма вида: if длина (х) = 1

then х else merge (merge-sort (первая-половнна (х)), merge.sort (вто­рая, половина (х)))

276 Глава 13

Мы можем также привести соответствующую ссылку на лите­ратуру, например: «Реализация сортировки с использованием метода сортировки слиянием» из книги: Кнут Д. Искусство про­граммирования для ЭВМ, т. 3,разд. 5.2.4.—М.: Мир, 1976. Важным критерием оценки проекта является степень легкости его тестирования. В разделах рабочего журнала для каждой из абстракций должен указываться предполагаемый способ тести­рования. Эта информация может предполагать тестировочные данные, описание драйвера, используемого для ввода провероч­ных данных и проверки результата, а также различные сообра­жения по поводу заглушек, использующихся для подмены вспо­могательных абстракций.

В разделе «Дополнительная информация» могут содержаться, например, следующие типичные сведения: 1) пояснения к реше­ниям, описанным в других разделах; 2) обсуждение рассмотрен­ных и отвергнутых альтернатив; 3) потенциальные расширения или иные модификации абстракции; 4) информация о контексте, в котором предполагается использоваться абстракция.

Завершая данный раздел, мы должны отметить, что в том случае, когда объемы по проектированию cлишкo^л велики, бывает полезно структурировать рабочий журнал, дополнительно заведя несколько вспомогательных. В диаграмме модульной зависимости любой подграф может рассматриваться как независимая подсис­тема. Однако наиболее удобно выбирать подграф, в котором доступ извне делается только к одному узлу.

13.3. Описание проблемы

В оставшейся части главы мы выполним полное проектирование простого форматировщика текста. Эта программа является не­большой и предназначен.-] главным образом для иллюстрации про­цесса проектирования. В данном разделе приводится специфика­ция форматировщика. В последующих описывается его проекти­рование. Реализация приведена в приложении Б. Входные дан­ные для форматировщика текста состоят из последовательности неформатированных строк текста и командных строк.

Каждая строка (за исключением, может быть, последней) завершается символом начала новой строки, а строка с командой начинается с точки, что отличает ее от строк с текстом. Например;

Выключка строк производится только в режиме заполнения. В режиме без заполнения каждая вводимая строка текста выводится без ' модификации.

команда .brвызывает переход к следующей строке. .brКак в данном случае.

Проектирование

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

Выходной текст отстоит от левого края на десять пробелов и разбивается на страницы по 50строк в каждой. В начале каждой страницы делается заголовок из 5строк. Заголовок состоит из 2 пустых строк, строки видаg. п

где п есть номер страницы, и еще двух пустых строк.

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

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

Входная строка начинается а символа перевода строки, вы­зывающего переход на новую строку; если текущая выходная строка не пуста, то она не заполняется и не выравнивается, а вы-

278 Глава 13

^Проектирование

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

.br вызывает переход на следующую строку

.nf вызывает переход на следующую строку и установку режима без запол­нения

.fi вызывает переход на следующую строку и устанавливает режим с за­полнением

Неопознанная команда приводит к выдаче сообщения об ошибке и игнорируется. Сообщение описывает ошибку и приводит номер строки, в которой она произошла.

Спецификация форматировщика приведена на рис. 13.2. По­скольку форматировщик предназначен для самых различных текстор, то мы не делаем никаких замечаний по поводу входных данных (т. е. предложение requires отсутствует). Спецификация описывает назначение процедуры, ссылаясь на информацию в дан­ном разделе; спецификация, заносимая в рабочий журнал проек­тировщика, будет содержать данный текст. Ограничения по эф­фективности определены следующим образом: время работы опре­делится как порядок (п), где п есть число символов в ins. Добав­ление пробелов добавит порядок (п) (память под outs), но допол­нительный объем временно используемой памяти будет неболь­шим, гораздо меньше чем порядок (п). Наконец, проект должен предусматривать такие потенциальные модификации, как введе­ние дополнительных команд (например, команды для размещения рисунков и команды смены шрифта), а также работу с различными типами устройств вывода. Также он должен быть достаточно прост и иметь модульную структуру.

format = proc (ins, outs, err: stream) signals (badarg (string)) modifies ins, outs, errs

effects Если ins закрыто на чтение или outs или errs закрыты для записи, то выдается сигнал через badarg (s), где s указывает неправильно откры­тый аргумент [например, badarg («входной поток»)]. В противном слу­чае программа format продолжает работу так, как это описано в тексте, принимая входные данные из ins, и помещает выходные данные в outs, а сообщение об ошибках — в errs. Перед возвратом ins, outs и errs за­крываются.

Рис. 13,2. Спецификация форматировщика.

13.4. Начальная стадия проектирования

В последующих разделах процесс проектирования будет про­иллюстрирован на примере разработки программы на языкеCLU, реализующей форматировщик текста. Это представление

'.будет несколько идеализированным; например, не будет допущено .никаких ошибок. Мы обсудим это в разд. 13.9.

Приступая к проектированию, мы начинаем с диаграммы мо­дульной зависимости, содержащей абстракции, определенные в процессе анализа требований. Этот граф может содержать дуги, поскольку некоторые абстракции могут зависеть от других. (На­пример, в спецификации требований для учетной системы может быть указан процессор команд как процедура и база данных — как тип данных; процессор команд зависит от базы данных, по­скольку он делает обращения к операциям из базы данных.) Мы начнем проектирование с занесения этой диаграммы и специ­фикаций абстракций в рабочий журнал. Затем наметим первую задачу. Поскольку в нашем случае имеется только одна абстрак­ция —format, то она автоматически становится нашей первой за­дачей.

На первом шаге необходимо определить дополнительные аб­стракции. Имеется одно эвристическое правило, которое может помочь нам в этом процессе; пусть структура задачи определяет структуру программы. Это означает, что мы должны сосредото­чить все внимание на решаемой задаче и попытаться разработать структуру программы.

Изучение структуры программы удобно начинать с составле­ния списка задач, которые должны быть решены. Ниже приводится список для задачиformats

1.Считать входные данные.

2.Интерпретировать их.

3.Получить выходные данные.

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

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

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

280 Глава 13

актирование

итераторы, для этой цели лучше всегод(у^олктипы данных.' Мы будем использовать типы в подходяш.-^ ^ этого местах: входных, выходных, внутренних и внеш^^ "^ах данных и отдельных элементах данных. Например, мь^ моя»,, использовать тип данных для скрытия подробностей обр^^™' выходных дан­ных (задача 3).Сначала мы должны реши^' ^й уровень ин-капсулирования принять. Мы можемвве^™^тракциюр.ля етроки, скрывающую форматированиео^^.е.пъщстроки, или абстракцию страницы, скрывающую форм^^Р^пие отдельной страницы. Ни один из этих приемов не поз^^^молностью ин­капсулировать выходные данные, и хотя по.^"^ ^страгирование представляется весьма заманчивой возмож^^™^ так как при этом облегчается получение выходных дани^^' ^ако оно было бы полезно только в том случае, если бы м^^^^ло ограничи­ться изменением только одной абстракции (^ ^at). В данном случае нашим решением будет введение тип^лащ\doc, инфор­мированного о характере входных данных, "^^йщего выход­ные данные в соответствии с ним. Типdocсх^^ ^ ^ким абстракт­ным устройством вывода и операциями высо^°™ ^овня, хорошо приспособленными для форматирования.

Аналогично тому как типdocинкапсУ-^РУ^ подробности обработки выходных данных, нам хотелось "^ ^Целить и под­робности обработки входных данных. Можн^ "^бы изобрести абстракцию признаков, представляющую ос^^^йые элементы во входном тексте, подобные словам и симв^^" ^буляции. Од­нако для создания подходящего признака м^ ^^ны знать, на­ходимся ли мы в конце строки, поскольку в э"°" ^сте точка ин­терпретируется иначе. В действительности Р^^^риваемая та­ким образом проблема предлагает строкоор^^^ованный под­ход. Если мы примем такую структуру, то зп^^ ^ущее положе­ние в строке будет довольно просто. Поэтому о^Р^Д^им процедуруdoJine, обрабатывающую входные данные по^Р^о. Программаformatбудет отвечать за обработку всехвхор-^^^рок, а проце­дураdoJineбудет отвечать за обработку ^^^^ых строк.

Наконец, мы должны рассмотреть п. 2,ка^^^ся интерпре­тации входных данных и взаимосвязи между "Р°^НуройdoJ'ineиdoc. Простым решением может быть возло^^^haпроцедуруdoJineзадачи интерпретации входных даннь^ "^зова соответ­ствующих операций для типаdoc. Такоеpeill^™^ ^ебует, чтобы мы передавали процедуре doJineобъектdocв качеств аргумента. Программаformatответственна за порожЛ^^ и окончание процесса выдачи выходных данных. Типdoc"°^рживает опе­рациюcreate, инициирующую вывод, а также ^"^Р^юterminate, завершающую его.

Теперь мы можем перейти ко второму ша^У "Р^ктирования, а именно утверждению и документирования ^"^ абстракций.

процессе документирования уточняются пропущенные детали. „екоторые из них просто являются неучтенными, а некоторые Гусловлены ошибками проектирования и требуют модификаций. ругими словами, в процессе документирования выполняется "сьма большой объем работы. Например, при спецификации роцедурыdo.lineмы должны точно решить, как позициопи-уется входной поток при вызове и возврате из процедуры. Мы злжны также решить, какой модуль ответствен за проверку соот-этетвия различных потоков аргументов требуемым. Далее, „гобходимо решить, должна ли процедура- doJineвыдавать сосб-дения об ошибке, а также то, каким образом задачаformatуз-(ает, что все входные данные были обработаны.

Спецификация процедуры doJineприведена на рис. 13.3, •де указаны также ограничения по эффективности. Эта специфика­ция содержит ответы на наши вопросы. Программа doJineот-«етственна также и за сообщения об ошибках, исключая проверку «ежимов дляinsиerrs, а также за закрытие их.Insустанавли-йется на начало строки как при вызовеdo.line, так и при воз­врате из нее. Процедура doJineсообщает процедуреformatо за-ершении ввода входного потока путем инициирования исключи-ельной ситуации.

o_line==proc (ins: stream, d; doc, err: stream) signals (all. done) requires == can. read (ins) & can-write (errs) &

d не было завершено. modifies ins, errs, d

effects Если ins пуст, то вызывается all-done. В противном случае обраба­тывается одна входная строка из ins в d, как это определено в специфика­ции format, а сообщения об ошибках записываются в errs. Обрабатывается вся строка, включая символ конца строки.

efficiency Время и объем памяти для просмотра и обработки одной строки пропорциональны числу символов в строке,

1'Рис. 13.3. Спецификация процедуры do. line.

I

1Спецификация дляdocприведена на рис. 13.4.Спецификации Операций приведены только дляcreateиterminate. Остальные ^спецификации будут приведены позднее. Отметим, что типdoo'предполагает проверку программойformatвозможности записи ^outs.Этотребование необходимо по той причине, что типd^c;должен иметь контроль над записываемой в поток информацией.'В настоящий момент наше представление о типеdocдостаточно неполно. При введении абстракций данных мы определяем только те используемые в реализации операции, которые изучаем в на­стоящий момент. Дополнительные операции будут введены при изучении других использующих данный тип реализаций. Отметим,чтоспецификацииdocи doJineне ссылаются к процедуреformat. ^ Такие ссылки типичны для спецификаций вспомогательных аб­стракций. Ссылки лучше, чем копирование, поскольку они ведут

Й82 Глава 13

doc == data type is create, terminate requires

Фактический выходной поток, связанный с аргументом outs операции create, не должен модифицироваться вне doc в период времени между созданием объ­екта doc и моментом прекращения к нему доступа.

Описание

Doc представляет собой абстрактное выводное устройство, выдающее фор­матированный текст (как было определено в спецификации для format), соот­ветствующее информации, передаваемой ей через операции. Объект doc свя­зан с выходным потоком, передаваемым к операции create. К моменту возврата из операции terminates все выходные данные должны быть помещены в этот поток.

Операции

create = proc (outs: stream) returns (doc)

requires can. write (outs) modifies outs

effects Готовится создать документ для outs, начиная со страницы 1 в режиме с заполнением.

terminate == proc (d; doc) requires terminate (d) до этого не вызывался. modifies d

effects Заканчивает запись информации из d в связанный с ней выход-ной поток, а затем закрывает этот поток.

end doc

efficiency время = порядок (п), где п есть число символов, записываемых в вы­ходной поток. Дополнительная память == порядок (п). Временный исполь­зуемый объем памяти гораздо меньше, чем порядок (п).

Рис. 13.4. Частичная спецификация для doc.

к созданию более кратких спецификаций, позволяющих создать более ясную абстракцию.

Расширенная диаграмма модульной зависимости приведена на рис. 13.5.Диаграмма содержит узлы дляdocиdoJineи дуги, определяющие их использование. На этом этапе проектирования мы знаем, что процедураformatиспользует типdocи процедуруdoJineи что объектdocпередается к doJine.Если абстракция использует объект некоторого типа как аргумент или результат, то она, следовательно, использует этот тип. На приведенной ди-

Рис. 13.5. Расширенная диаграмма модульной зависимости,

Проектирование

аграмме doJineиспользуетdoc, что показано дугой от doJine кdoc, при этом doJineнаходится на более высоком уровне ди­аграммы, чемdoc. Набросок реализацииformatприведен на рис. 13.6.

проверить типы аргументов d: doc :== doc$create (outs)

call do_line (ins, d, errs) до тех пор, пока она не выдаст сигнал alL done» Завершить d и закрыть ins и errs.

Рис. 13.6. Набросок реализации процедуры format.

13.5. Обсуждение метода

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

Мы ввели абстракции для сокрытия подробностей, представ­ляющихся несущественными на текущем уровне. При этом воз­никает вопрос о том, каким образом решить, что считать сущест­венным на текущем уровне. Данное решение не поддается форма­лизации, однако относительно него можно сделать ряд замечаний. Разумеется, ряд проблем решит реализация, но часть из них тем не менее останется. Наша цель —прийти к небольшим моду­лям, Реализации процедур и операторов внутри и снаружи типов данных должны иметь размер, не превышающийодной-де^х страниц. Если в программеformatмы решили позаботиться о под­робностях ввода и вывода, то модуль получился бы чересчур большим. Кроме этого, различные части реализации модуля дол­жны находиться приблизительно на одном и том же уровне дета.ли-зации. Даже обрабатывая входные данные построчно, программаformatхорошо сбалансирована с этой точки зрения, поскольку эти решения вписываются в структуру программы.

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

284 Глава 13

Проектирмание

модействуют через объекты некоторого типа, но при этом неизвест­но, какие операции над этими объектами окажутся полезными. Следовательно, спецификация разделяемого типа является необ­ходимо неполной. Она будет завершена в процессе проектирования.

13.6. Последующие стадии проектирования

Теперь у нас имеется диаграмма модульной зависимости, со­держащая несколько абстракций. Каким должен быть следующий шаг? Первое, что можно отметить, это тот факт, что на следующем шаге в качестве целевых годятся не все абстракции. Назовем кандидатами те из них, которые удовлетворяют необходимым требованиям. Очевидно, что сама процедура format не может быть кандидатом, поскольку мы уже спроектировали её реализацию. Кроме этого, doc также не может быть кандидатом, поскольку она незавершена. (Нам придется выбирать в качестве кандидата неполную абстракцию — см. разд. 13.9.) У нас остается единствен­ный кандидат — do_line. Абстракция doJine должна обраба­тывать строку текста или строку команды. Эти два случая су­щественно отличаются друг от друга, поэтому для их обработки мы введем две процедуры, do_text_line и do_command. Хотя об­работка команды и строк текста может производиться непосред­ственно, добавление этих двух процедур может упростить буду­щие модификации, облегчая локализацию изменяемых участков, например в случае добавления новой команды. Спецификации этих процедур приведены на рис. 13.7. Отме-

do_ text. line == proc (ins: stream, d: doc)

requires can-read (ins) &—empty (ins) & d еще не было завершено. modifies ins, d

effects Считывает одну строку из ins, включая символ конца строки, и обра­батывает ее как текст, согласно определению в спецификации для do-line. do-command = proc (ins: stream, d: doc) signals (error (string)) requires can-read (ins) &— empty (ins) & d еще не было завершено. effects Считывает одну строку из ins, включая символ конца строки. Удаляет первый символ и обрабатывает оставшуюся часть строки как строку команды, как это определено в спецификации do- line. Если команда оши­бочна, то об этом сигнализируется через error (s), где s описывает ошибку. Однако, даже если команда ошибочная, обрабатывается вся строка.

Рис. 13.7. Спецификации операций do-text-line и do-command.

тим, что какdo_text-line, так и do_commandполагают, что при их вызовеinsустановлен на первый символ строки. Это означает, что процедура do_lineможет только читать первый символ, не удаляя его, т. е. можно воспользоваться процедуройstream$peekc, а неstream$getc.

Новая диаграмма модульной зависимости приведена на рис. 13.8 Теперь на ней имеется пунктирная дуга от do_lineкdoc, посколькуdo_lineне вызывает изdocникаких операций. Приступая к ана-

Рис. 13.8. Диаграмма модульной зави­симости после проектирования аб­стракции do-line.

лизу do_line,мы не знаем пока, будет ли использованиеdooсильным или слабым, поэтому полагаем, что оно будет сильным. Аналогичные предположения можно сделать и относительноdo.text-lineиdo-command.

Теперь мы располагаем двумя кандидатами —do_text-lineи do_command.Какого из них выбрать? Четких и строгих правил здесь не существует: можно остановиться на любом кандидате. Однако имеется несколько причин, позволяющих выделить более предпочтительную кандидатуру:

1.Мы пока не знаем, как реализовывать процедуру. Напри­мер, нам может потребоваться реализовать некоторую абстрак­цию очень эффективно, однако мы не знаем, как достичь этой эффективности, и даже вообще, достижима ли она.

2.Мы не вполне уверены в том, что она нам подходит.

3.Она может оказаться более предпочтительной для нашего проекта, чем другая, поэтому изучение ее реализации облегчит проектирование.

4.Нам хотелось бы целиком закончить работу с какой-то одной, уже начатой частью проекта.

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

В нашем примере различие между двумя кандидатами невелико, поэтому мы можем достаточно произвольно выбрать процедуруdo_text_line. Основной вопрос заключается в том, как передавать информациюdocо входных данных. Одним из способов может быть передача некоторого опознавателя, кодирующего эту ин­формацию. Однако такой подход ведет к дополнительной работе;

286 Глава 13

процедураdo_text_lineдолжна кодировать информацию, которая затем должна быть декодирована вdoc. Гораздо лучше обеспечить вdocоперации для каждого такого случая. Это решение более эффективно и по пространственно-временным характеристикам.

Кdocпередается информация следующих типов: слова, про­белы, символы табуляции, конец строки, перевод строки (если первый символ в строке есть пробел или символ табуляции) и пустая строка (если вся строка пуста). Каждый из этих типов дол­жен обрабатываться отдельной операцией:add_word,add.space,add_tab,add.newline,breakJineиskip_line. Преимущество такой схемы заключается также и в том, что она легко расширяема.

Другой вопрос касается представления слов: могут ли слова быть строками, или нам необходима абстракция данных? Мы могли бы использовать абстракцию данных, если бы это облег­чило дальнейшие модификации, однако в данном случае это не так. Например, абстракция слова не поможет в добавлении до­полнительных шрифтов, поскольку изменение шрифта обычно приводит к одновременному изменению нескольких слов. Поэтому для слов мы будем использовать строки. Оставшаяся часть про­цедурыdo_text_lineкасается просмотра входных данных, однако она достагочно проста и не требует вспомогательных абстракций.

Процедураdo^commandдолжна отыскивать команду во вход­ном потоке и отделять ее от оставшейся части строки. Основной вопрос здесь заключается в том, должна ли процедура рас­познавать команду или просто передавать строку для обработки ее операцией изdoc. В данном случае подходит любое из решений. Однако по мере добавления дополнительных команд, возможно, потребуется синтаксический анализатор (поскольку команды мо­гут иметь аргументы). Это анализ удобно возложить на процедуруdo_command, посколькуdocжелательно освободить от подобных функций. Следовательно, процедураdo.commandдолжна рас­познавать команды. Затем она может вызывать изdocнеобходи­мые операции. К ним относятсяbreakJine,set_fillиseLnofill.

Спецификацияdocприведена на рис. 13.9.Диаграмма модуль­ной зависимости показана на рис. 13.8.

13.7. Абстракция doc

На данный момент абстракцияdocявляется единственным кан­дидатом, поэтому мы можем приступить к рассмотрению ее пред­ставления. Для выбора представления необходимо решить следую­щий вопрос: выдавать ли отформатированный текст построчно или же целиком после полной обработки? Поскольку дожидаться конца обработки нет необходимости, мы выберем построчный под­ход. Это также позволит соблюсти предъявляемые кформатиров-щику пространственно-временные ограничения.

Проектирование

doc = data type is add.word, add.space, add_tab, add.newline, break.line,

skip-line, set-till, set-nofill, terininate

requires

фактический выходной поток, связанный с аргументом outs операции create, не должен модифицироваться вне doc в период времени между созданием объ­екта doc и моментом прекращения к нему доступа,

Описание

Doc представляет собой абстрактное выводное устройство, выдающее форма­тированный текст (как было определено в спецификации для format), соот" ветствующее информации, передаваемой ей через операции. Объект doc свя­зан с выходным потоком, передаваемым к операции create. К моменту возврата из операции terminates все выходные данные должны быть помещены в этот поток.

Операции

create = proc (outs: stream) returns (doc)

requires can-write (outs). modifies outs

effects Готовится создать документ для outs, начиная со страницы в режиме с заполнением.

add-word == proc (d: doc, w: string) requires d не был закрыт. modifies d effects Добавляет w к d.

add. space = proc (d: doc) requires d не был закрыт. modifies d effects Добавляет к d пробел.

add. tab = proc (d: doc) requires d не был закрыт. modifies d effects Добавляет к d символ табуляции.

ad<Lnewline = proc (d; doc) requires d не был закрыт. modifies d effects Добавляет к d символ конца строки.

skip. line == proc (d: doc) requires d не был закрыт. modifies d effects Добавляет к d пустую строку,

break- line =prec (d: doc) requires d не был закрыт. modifies d effects Добавляет к d символ перевода строки.

set_fill == proc (d: doc) requires d не был закрыт. modifies d

effects Добавляет к d символ перевода строки и производит переход в режим с заполнением.

288 Глава 13

Проектирование

set-nofill = proc (d: doc) requires d не был закрыт. modifies d

effects Добавляет к d символ перевода строки и производит переход в режим без заполнения.

terminate = proc (d: doc) reduires terminate (d) до этого не вызывался. modifies d

effects Заканчивает запись информации из d в связанный с ней выход­ной поток, а затем закрывает этот поток.

end doc

Эффективность. Время = порядок (п), где п есть число символов, записываемых в выходной поток. Дополнительная память == порядок (п). Временный исполь­зуемый объем памяти гораздо меньше, чем порядок (п).

Рис. 13.9. Полная спецификация для doc,

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

Абстракцияdocдолжна выполнять две различные функции! обрабатывать строки и организовывать строки в страницы. Боль­шинство сложностей связано с обработкой строк, а именно запол­нением и выравниванием строки в режиме с заполнением. Введе­ние абстракцииlineпозволит изолировать обработку строки. Инкапсуляция этих подробностей позволит нам не рассматри­вать их в реализацииdoc.

Мы можем изолировать подробности обработки на нескольких различных уровнях. К одной из возможностей относится абстрак­ция высокого уровня, которая выравнивает и выводит строку авто­матически после ее заполнения. Такая абстракция должна быть информирована о текущем установленном режиме (с заполнением или без), а также знать максимальную длину строки. Отметим, что вывод строки не может быть изолирован полностью; абстрак­циюdocнеобходимо информировать о выводе строки с тем, чтобы она могла позаботиться о форматировании страницы. Альтерна­тивный подход предполагает наличие абстракции более низкого уровня, следящей за словами и пробелами между ними, но не выполняющей выключку строк. Однако подобная абстракция дает небольшие преимущества по сравнению с простым хранением ин­формации в массиве. Мы воспользуемся абстракцией строкиl.ne—третьим подходом, промежуточным по отношению к двум т)лько что рассмотренным. Этот тип будет обеспечивать выключкуi. ;рок, но только по команде. Информация о текущем режиме об­работки и длине строки будет сохраняться вdoc, а не вline. Это решение кажется подходящим, поскольку эта информация от-

носится к документу в целом, т. е. к информации на уровне доку-. мента. Отметим, что мы добились неплохого распределения проб­лем: абстракцияlineзаботится обо всем, что касается строк, а абстракцияdocзаботится обо всем том, что касается форматиро­вания документа.

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

Для определения операций дляlineмы должны рассмотреть реализации операций дляdoc. Для обработки пробелов и символов табуляции будут полезныline§add-spaceиline$add_tab. Однако большинство взаимодействий между абстракциейlineиdocпро­исходит вdoc$add_word. Приблизительная схема этой операции имеет следующий вид:

if режим с заполнением и на строке нет места для слова

then выровнять и вывести строку end добавить слово к строке

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

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

Последний вопрос касается создания объектов типа «строка». Очевидно, что для начала нам необходима операция create. После

10 Лисков Б,, Гатэг Дж.

290 Глава 13

line = data type is create, add. word, add.space, add.tab, length, justify, output,

clear

Описание

Line представляет собой изменяемую строку текста, состоящую из символов, соответствующих словам, символам табуляции и пробелам. Операция justify выравнивает строку по правому краю на заданную длину, а операция output записывает строку в выходной поток.

Операции

create == proc ( ) returns (line) effects Возвращает новую, пустую строку.

add. word = proc (1: line, w: string)

modifies I effects Добавляет символы из w к концу I,

add_ space = proc (1: line)

modifies I effects Добавляет пробел в конец 1.

add_tab = proc (1: line) modifies I

effects Добавляет к концу 1 символ табуляции. Это предполагает добавление к концу 1 от 1 до 8 пробелов. После этого длина (I post) mod 8 = 1.

length = proc (1: line) effects Возвращает число символов в 1.

justify = proc (1: line, len: int, from-right: bool)

modifies I

effects Выравнивает 1, как это было описано в спецификации format так, чтобы в 1 содержалось len символов, если это возможно. Выравнивание производится добавлением дополнительных про­белов справа от крайней правой метки табуляции. Пробелы доба­вляются по возможности равномерно, начиная с правого края, если from-right имеет значение true, и слева — в противном слу­чае, Если выравнивание невозможно (например, если в 1 только одно слово), то 1 оставляется без изменений.

= proc (1: line, outs: stream) requires can-write (outs). modifies outs effects Переписывает символы из 1 в outs.

clear == proc (1: line) modifies I effects Повторно инициализирует 1, очищая ее.

end line

Эффективность. Добавляемое пространство имеет порядок (п), где п есть число символов в строке. Время, расходуемое на выравнивание и вывод, должно быть порядка (п). Время, расходуемое на другие операции, должно быть по­стоянным,

Рис. 13.10. Спецификация для line.

вывода строки мы можем создать для следующей строки другой объект, но такой подход бесполезен, поскольку он приводит к накоплению «мусора» (старых строк), хранить которые нет не-

output

лДрмктирование

% Doc осуществляет вывод построчно, используя буфер, представляющий % объект из одной строки. '" Текущая строка

гер = record [line: line,

fill: bool, from-right: bool,

lines- per- page

lineno: int, pageno: int, outs: stream] 50

% Истинно в режиме с заполнением % Истинно, если дополнительные про-% белы добавляются справа % Число строк на текущей странице % Номер текущей страницы % Выходной поток

% Хранится в виде константы, поэтому легко % может быть изменено

% Типичный объект doc представляет собой последовательность символов % из всех обработанных строк, завершаемую символами из текущей строки. % Функция абстракции имеет следующий вид: % Символы в d.outs li символы в d.line % Инвариант представления есть can-write (г.outs) & I <r.lineno <= lines-per-page & 0 < = r .pageno & r .page- no ;> 0 ==>

Текущая страница имеет соответствующий заголовок $ r.line-no строк, не считая заголовка. предыдущих страниц р (т, е. страниц с номерами 1, .,., r.page-no

р имеет соответствующий заголовок & р имеет lines-per-page строк, не считая заголовка & за р следует символ перехода к следующей странице.

% Внутренняя процедура output-line, ответственная за форматирование стра-% ницы по мере обработки строк. output- line = proc (d: rep)

modifies d

effects Записывает d.line и символ конца строки в d.outs, увеличи­вает на единицу d.lineno и d,pageno, а также заботится о заго­ловках и переходах к следующей странице. Рис. 13,11. Реализация документации для абстракции doc.

обходимости. Лучше инициализировать и использовать старый объект повторно. Это позволяет сделать операцияclear. Специфи­кация дляlineприведена на рис. 13.10.

Операцияdoc$add_wordне является единственной операцией вdoc, позволяющей вводить строки. Это могут делать также опе­рацииaddJine,break_line,skip-lineиterminate. Вывод строк предполагает также форматирование страницы. Для выполнения этих функций удобна процедураoutput_line. Поскольку вне реа­лизацииdocпроцедура output_lineвряд ли окажется полезной, она может быть внутренней по отношению к реализации. Это означает, что мы не добавляем ее к диаграмме модульной зависи­мости, а проектируем ее реализацию как часть реализацииdoc.

Процедура output_lineответственна за все операции форма­тирования, поэтому мы должны принять решение относительно того, как следить за номерами страниц и выявлять необходимость перехода к новой странице. Разумно хранить номер текущей

10*

292 Глава 13

Рис. 13.12. Диаграмма модульной за­висимости с добавленным модулем line.

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

Реализация абстракцииdocприведена на рис. 13.11.Отме­тим, что приводится не только представление, инвариант представ­ления и функция абстракции, но также и спецификация внутрен­ней программыoutput„line. Расширенная диаграмма модульной зависимости приведена на рис. 13.12.

Соседние файлы в папке Б. Лисков