Windows API и утилит отображения на дисплее компьютера в поезде.
Основное достоинство предлагаемого подхода заключается в том, что уменьшается влияние изменений, возникающих при перераспределении роли аппаратуры и программ. Например, если нам надо заменить наши дисплеи на более (менее) мощные, придется подправить процедуры только в классе TrainDisplayUtilities. Без такой декомпозиции нам бы пришлось вносить изменения в каждый отображаемый объект при любых изменениях на нижнем уровне.
Механизм опроса датчиков
Выше мы говорили, что система управления движением должна включать в себя большое количество разнообразных датчиков. Например, на каждом поезде датчики следят за температурой масла, количеством топлива, дроссельной установкой, температурой воды, нагрузкой на двигатель и т.д. Активные датчики путевых устройств сообщают текущее положение своих переключателей и передают сигналы. Все значения, возвращаемые датчиками - разные, но их обработка может производиться сходным образом. Допустим, что наш компьютер использует ввод-вывод по фиксированным адресам памяти. Тогда данные каждого датчика читаются из определенной области памяти и только потом интерпретируются способом, зависящим от конкретного датчика. Большинство датчиков должно опрашиваться периодически. Если значение находится в заданных пределах, оно сообщается какому-то клиенту, и больше ничего не происходит. Если же отсчет датчика вышел за установленные пределы, об этом могут быть оповещены и другие клиенты. Наконец, если отсчет вышел далеко за допустимые границы (например, давление масла на локомотиве поднимается до опасного уровня), может понадобиться какой-то звуковой сигнал тревоги и уведомление специальных клиентов для принятия решительных мер.
Воспроизведение этого поведения для каждого датчика не только утомительно и чревато ошибками, но и раздувает объем кода. Если мы с самого начала не выделим общие для всех датчиков характеристики, то различные разработчики предложат свои решения одной и той же задачи. Это, в свою очередь, приведет к сложностям при сопровождении системы. Поэтому для выявления общих свойств необходимо провести анализ всех периодически опрашиваемых аналоговых датчиков и предложить общий механизм их опроса, приемлемый для всех.
Мы уже решали аналогичную задачу в главе 8, применительно к метеорологической станции. Там мы создали иерархию классов датчиков и описали механизм их опроса. Есть все основания воспользоваться полученным ранее решением и в нашей нынешней задаче.
Это хороший пример повторного использования проектных решении в различных прикладных областях.
12.3. Эволюция
Модульная архитектура
Мы уже говорили о том, что модульность для больших систем необходима, но не достаточна; для задач такого масштаба, как система управления движением, нужно сосредоточиться на декомпозиции по подсистемам. На ранних стадиях эволюции мы должны разработать модульную архитектуру системы, представляющую физическую структуру ее программного обеспечения.
Проектирование программного обеспечения для очень больших систем должно начинаться до полного завершения проектирования аппаратных средств. Написание программы занимает, как правило, даже больше времени, чем разработка аппаратуры. Кроме того, по ходу процесса функциональность может перераспределяться между аппаратной и программной частями. Поэтому зависимость от аппаратуры должна быть максимально изолирована, так, чтобы программные средства можно было начать проектировать без привязки к аппаратуре. Это означает также, что разработка должна основываться на идее взаимозаменяемых подсистем. В системах управления и контроля, таких, как система управления движением, нужно сохранить возможность задействовать новые аппаратные решения, которые могут появиться в процессе разработки программного обеспечения.
На ранних этапах мы должны разумно провести декомпозицию программного обеспечения, чтобы субподрядчики, ответственные за различные части системы, могли работать одновременно (возможно даже используя различные языки программирования). Как уже говорилось в главе 7, существует много причин нетехнического характера, определяющих физическую декомпозицию больших систем. Наиболее важен вопрос взаимодействия различных групп разработчиков. Отношения между субподрядчиками складываются обычно на достаточно ранних стадиях жизни системы, часто до получения информации, достаточной для выбора правильной декомпозиции системы.
Желательно, чтобы системные архитекторы поэкспериментировали с несколькими альтернативными декомпозициями на подсистемы для того, чтобы быть уверенными в правильности глобального решения по физическому проектированию. Можно задействовать прототипирование в больших масштабах с имитацией подсистем и моделированием загрузки процессора, маршрутизации сообщений и внешних событий. Прототипирование и моделирование могут послужить основой для нисходящего тестирования по мере создания системы.
Как выбрать подходящую декомпозицию на подсистемы? В главе 4 отмечено, что объекты на высоком уровне абстракции обычно группируются в соответствии с их функциональным повелением. Еще раз подчеркнем, что это не противоречит объектной модели, так как термин функциональный мы не связываем жестко с понятием алгоритма. Мы говорим о функциональности как о внешнем видимом и тестируемом поведении, возникающем в результате совместной деятельности объектов. Таким образом, абстракции высокого уровня и механизмы, о которых говорилось ранее, хорошо подходят на роль подсистем. Мы можем сначала допустить существование таких подсистем, а их интерфейс разработать через некоторое время.
На диаграмме модулей на рис. 12-9 представлены проектные решения верхнего уровня модульной архитектуры системы управления движением. Каждый уровень здесь соответствует выделенным ранее четырем подзадачам: сеть передачи данных, база данных, аналоговые устройства управления в реальном времени, интерфейс 'человек/компьютер'.
Спецификация подсистем
Если мы рассмотрим внешнее представление любой из подсистем, то обнаружим, что она обладает всеми характеристиками объекта. Каждая подсистема имеет уникальную, хотя и статичную, идентичность и большое число возможных состояний, а также демонстрирует очень сложное поведение. Подсистемы используются как хранилища других классов, утилит классов и объектов; таким образом, они лучше всего характеризуются экспортируемыми ресурсами. На практике при использовании C++ эти ресурсы представляются в форме каталогов, содержащих логически связанные модули и вложенные подсистемы.
Диаграмма модулей на рис. 12-9 полезна, но не полна, так как каждая из подсистем на ней слишком велика для реализации одним небольшим коллективом разработчиков. Мы должны раскрыть внутреннее представление подсистем верхнего уровня и провести их декомпозицию.
Рассмотрим для примера подсистему NetworkFacilities (сеть). Мы решили разбить ее на две другие подсистемы, одна из которых - закрытая (RadioCommunication (радиосвязь)), а другая - открытая (Messages (сообщения)). Закрытая подсистема скрывает детали своего программного управления физическими устройствами, в то время как открытая подсистема обеспечивает поддержку спроектированного ранее механизма передачи сообщений.
Подсистема, названная Databases (базы данных), построена на основе ресурсов подсистемы NetworkFacilities и служит для реализации механизма планов движения поезда, который мы описали выше. Мы составляем эту подсистему из двух экспортируемых открытых подсистем, TrainPlanDatabase (база данных планов поездов) и TrackDatabase (база данных путей). Для действий, общих для этих двух подсистем, мы