предусмотрим закрытую подсистему DatabaseManager (менеджер баз данных).
Подсистема Devices (устройства) также естественно разбивается на несколько небольших подсистем. Мы решили сгруппировать программы, относящиеся ко всем путевым устройствам, в одну подсистему, а программы, связанные с активными механизмами и датчиками локомотива, - в другую. Эти две подсистемы доступны клиентам подсистемы Devices, и обе они построены на основе ресурсов подсистем TrainPlanDatabase и Messages. Таким образом, мы спроектировали подсистему Devices для реализации механизма датчиков, который описан выше.
Наконец, мы представляем подсистему верхнего уровня UserApplications (прикладные программы) в виде нескольких небольших подсистем, включая EngineerApplications (программы для машиниста) и DispatcherApplications (программы для диспетчера), чтобы зафиксировать разную роль двух главных пользователей системы управления движением. Подсистема EngineerApplications содержит ресурсы, которые обеспечивают взаимодействие машиниста и компьютера, в частности, анализ системы сбора и отображения информации о состоянии локомотива и системы управления энергией. Подсистема DispatcherApplicatlona обеспечивает интерфейс 'диспетчер/компьютер'. Подсистемы EngineerApplications и DispatcherApplications разделяют общие закрытые ресурсы, экспортируемые из подсистемы Displays (отображение), которая реализует описанный ранее механизм отображения.
В результате проектирования мы получили четыре подсистемы верхнего уровня и десять подсистем следующего уровня, в которых размещены все введенные ранее ключевые абстракции и механизмы. Важно, что в терминах этих подсистем можно планировать работу, управлять конфигурациями и версиями. Как говорилось в главе 7, отвечать за каждую такую подсистему может один человек, в то время как разрабатывать ее будет множество программистов. Ответственный за подсистему детализирует ее проект и реализацию и управляет ее интерфейсом с другими подсистемами на том же уровне абстракции. Так, за счет декомпозиции сложной задачи на несколько более простых, становится возможным управление разработкой сложных проектов.
В главе 7 уже демонстрировалась возможность нескольких одновременных представлений разрабатываемой системы. Набор совместимых версий подсистем образует релиз, и таких релизов может быть множество - по одному на каждого разработчика, еще один - для тестирования, один - для опробования пользователями и т.д. Отдельные проектировщики могут для своих нужд создавать собственные стабильные релизы и интегрировать в них те части, за которые они отвечают, до передачи их остальным. Так создается механизм непрерывной интеграции нового кода.
Основой успеха является тщательное конструирование интерфейсов подсистем. После того как интерфейсы определены, они должны тщательно оберегаться. Как мы определяем внешнее представление подсистемы? Нужно каждую подсистему рассматривать как объект. Поэтому мы ставим те же вопросы, которые задавали в главе 4 для значительно более простых объектов: Какие состояния имеет объект? Какие действия над ним может выполнить клиент? Каких действий он требует от других объектов?
Например, рассмотрим подсистему TrainPlanDatabase. Она строится на основе трех других подсистем (Messages, TrainDatabase, TrackDatabase) и имеет нескольких важных клиентов - подсистемы WaysideDevices (путевые устройства), LocomotiveDevices (устройства на локомотиве), EngineerApplications и DispatcherApplications. Подсистема TrainPlanBatabase относительно проста - она содержит все планы поездов. Конечно, хитрость в том, что эта подсистема должна поддерживать механизм распределенной передачи планов движением поезда. Снаружи клиент видит монолитную базу данных, но изнутри мы знаем, что на самом деле база данных - распределенная, и поэтому должны основывать се на механизме передачи сообщений подсистемы Messages.
Какие действия можно выполнять с помощью TrainPlanDatabase? Все обычные для базы данных операции: добавление, удаление и изменение записей, запросы. Так же как в главе 10, нужно зафиксировать все проектные решения об этой подсистеме в форме классов C++, которые снабдят нас объявлениями операций.
На этой стадии нам следует продолжить процесс проектирования для каждой подсистемы. Еще раз отметим, что вероятность того, что все интерфейсы окажутся правильными с первого раза, очень мала. К счастью, как и для небольших объектов, опыт подсказывает, что большинство изменений, которые мы произведем в интерфейсах, не затронет верхних уровней (совместимость снизу вверх), если мы хорошо поработали, описывая каждую подсистему в объектно-ориентированном стиле.
12.4. Сопровождение
Добавление новых функций
Программное обеспечение сопровождается и постоянно дорабатывается, что особенно справедливо для таких больших систем, как наша. Действительно, до сих пор можно встретить программы, разработанные лет двадцать назад (просто патриархальные по компьютерным меркам). Чем больше пользователей применяет систему управления движением и чем лучше мы адаптируем проект к новым требованиям, тем чаще клиенты будут находить новые неожиданные применения для существующих механизмов, создавая потребность во включении в систему новых функций.
Рассмотрим единственное добавление к нашим требованиям: обработку платежной ведомости. Предположим, анализ показал, что работа с платежными ведомостями железнодорожной компании осуществляется с использованием аппаратуры, выпуск которой прекращен, поэтому возник серьезный риск безвозвратной потери всей системы платежей в результате нескольких критических поломок. В этом случае можно объединить обработку платежной ведомости с системой управления движением. Для начала надо понять, как эти две несвязанные задачи будут сосуществовать; можно рассматривать их как разные приложения, причем обработка платежной ведомости будет происходить в фоновом режиме.
Дальнейший анализ показывает, что от интеграции обработки платежной ведомости может быть получена огромная польза. Вспомним, что планы поездов содержат информацию о распределении бригад. Следовательно, мы можем проанализировать запланированное и действительное распределение бригад, вычислить рабочее время, сверхурочные часы и т. п. Получая эту информацию непосредственно, мы можем обрабатывать платежную ведомость дешевле и быстрее.
Что добавление этой функции затрагивает в нашем проекте? Очень немногое. Новую подсистему можно добавить в подсистему UserApplications. Оттуда новой подсистеме будут видны все важные механизмы, которые нужны для ее функционирования. Признак хорошо спроектированной объектно-ориентированной системы: значительные дополнения к требованиям могут быть учтены довольно просто путем надстройки новых функций над существующими механизмами.
Предположим, мы хотим ввести более существенное изменение: добавить экспертную систему, помогающую диспетчеру при определении маршрутов и реагирующую на чрезвычайные ситуации. Как это требование отразится на нашем проекте? Незначительно. Мы можем разместить новую подсистему между подсистемами TrainPlanDatabase и DispatcherApplications, так как база знаний, созданная для экспертной системы, подобна по содержанию TrainPlanDatabase; кроме того, подсистема DispatcherApplications является единственным клиентом экспертной системы. Нам предстоит разработать некоторый новый механизм, чтобы доводить рекомендации до конечного пользователя. Например, мы можем использовать метафору информационной доски, как это делалось в главе 11.
Изменение аппаратных средств
Мы уже говорили, что аппаратные средства развиваются быстрее, чем программное обеспечение. Более того, всегда будут причины, вынуждающие нас выбрать в ходе проектирования такие аппаратные решения, о которых потом мы будем сожалеть [Например, часть аппаратуры придется закупить у третьей