Основы объектно-ориентированного программирования - [50]

Шрифт
Интервал

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

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

Обнаружение вершины

Нисходящий метод проектирования предполагает, что каждая система характеризуется на самом абстрактом уровне своей главной функцией. Хотя многие учебные примеры алгоритмических проблем - "Ханойские башни", "Задача о 8 ферзях" и т. п. - действительно легко задать с помощью их "верхних" функций, более полезно описывать практические системы в терминах предоставляемых ими услуг.

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


>"Обработать все запросы пользователя",


который далее можно уточнять примерно так:


>from

>начальная загрузка системы

>until

>остановка или аварийный отказ

>loop

>"Прочесть запрос пользователя и поместить во входную очередь"

>"Взять запрос r из входной очереди"

>"Обработать r"

>"Поместить результат в выходную очередь"

>"Взять результат q из выходной очереди"

>"Выдать результат q получателю"

>end


Уточнения могут продолжаться. Однако маловероятно, что после такого начала кому-либо удастся спроектировать разумно структурированную операционную систему.

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

По-видимому, очевидная отправная точка проектирования сверху вниз - взгляд, согласно которому для каждой новой разработки требуется запросить некоторую специальную функцию - является весьма сомнительной:

У реальной системы нет "вершины"!

Функции и эволюция

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

Рассмотрим в качестве примера программу, имеющую две версии: одну "пакетную", которая выполняет во время сессии одно большое непрерывное вычисление, и другую - интерактивную, которая в каждой сессии реализует последовательность транзакций с разбиением взаимодействия пользователя с системой на более мелкие шаги. Большие научные программы очень часто имеют две версии: одну, которая "пусть работает всю ночь, выполняя большую порцию вычислений", и другую, которая "позволяет мне сначала проверить некоторые вещи, посмотреть на результаты, а затем вычислить еще что-нибудь".

Уточнение сверху вниз пакетной версии могло начаться следующим образом.


>[B0] - Абстракция верхнего уровня

>"Решить полный экземпляр проблемы"

>[B1] - Первое уточнение

>"Прочесть входные данные"

>"Вычислить результаты"

>"Вывести результаты"


и т. д. Проектирование интерактивной версии сверху вниз может происходить в следующем стиле.


>[I1]

>"Обработать одну транзакцию"

>[I2]

>if "Пользователь предоставил новую информацию" then

>"Ввести информацию"

>"Запомнить ее"

>elseif "Запрошена ранее данная информация" then

>"Извлечь запрошенную информацию"

>"Вывести ее"

>elseif "Запрошен результат" then

>if "Необходимая информация доступна" then

>"Получить запрошенный результат"

>"Вывести его"

>else

>"Запросить подтверждение запроса"

>if Да then

>"Получить требуемую информацию"

>"Вычислить запрошенный результат"

>"Вывести результат"

>end

>end

>end


(и т. д.)

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