Теперь связь между экземплярами классов Sensors
, DisplayManager и InputManager устанавливается в момент создания объекта класса Sampler. Использование ссылок гарантирует, что каждый экземпляр Sampler получит соответствующий набор датчиков, менеджера экрана и менеджера ввода. Другая схема, в которой вместо ссылок используются указатели, обеспечила бы довольно слабую связь, позволяя создавать объект Sampler, у которого отсутствовали бы некоторые важные компоненты.Ключевую функцию Sampler::sample
надо модифицировать следующим образом:void Sampler::sample(Tick t)
{repInputManager.processKeyPress();
for (SensorName name = Direction; name <= Pressure; name++)for (unsigned int id = 0; id < repSensors.numberOfSensors(name); id++)
if (!(t % samplingRate(name)))
repDisplayManager.display(repSensors.sensor (name, id));
}
В начало каждого кадра мы добавили вызов метода processKeyPress. Операция processKeyPress является точкой входа в конечный автомат, управляющий работой экземпляров класса InputManager
. Существуют два подхода к реализации любого конечного автомата: можно представить состояния системы объектами и положиться на их полиморфное поведение или просто ввести перечисление состояний, обозначив их литералами.Для конечных автоматов с относительно небольшим числом состояний, к числу которых принадлежит и класс InputManager
, достаточно использовать второй подход. Сначала определим имена объемлющих состояний класса:enum InputState {Running, Selecting, Calibrating, Mode);
Затем определим некоторые защищенные функции класса:
class InputManager {
public:...protected:Keypads repKeypad;
InputState repState;void enterSelecting();void enterCalibrating();void enterMode();};
И, наконец, начнем реализовывать переходы между состояниями (см. рис. 8-11):
void InputManager::process Keypress() {
if (repKeypad.inputPending()) {
Key key = repKeypad.lastKeyPress();
switch (repState) {case Running:if (key == kSelect)
enterSelecting();
else if (key == kCalibrate)
enterCalibrating();
else if (key == kMode)
enterMode();
break;
case Selecting: break;
case Mode: break;}}
}
Таким образом, реализация данной функции отражает содержание диаграммы переходов межу состояниями на рис. 8-11.
8.4. Сопровождение
Полная реализация рассматриваемой системы является не слишком объемной - всего около 20 классов. Тем не менее, для любого работающего фрагмента кода этап последующей модернизации неизбежен. Рассмотрим, что придется сделать, чтобы реализовать еще два дополнительных требования к нашей системе.
Видно, что система позволяет измерять многие погодные параметры, однако не все. Может оказаться, что пользователи захотят измерять также количество осадков. Какие изменения при этом необходимо будет внести в программу?
К счастью, нам не придется радикально менять нашу архитектуру, надо будет лишь дополнить ее. Используя в качестве основы архитектурный макет, представленный на рис. 8-13, можно выделить следующие необходимые изменения:
• Создание нового класса-датчика RainFallSensor
(датчика осадков); выявление его оптимального положения в иерархии датчиков (RainFallSensor есть разновидность HistoricalSensor).• Обновление перечисления SensorName.
• Модификация класса DisplayManager
, обеспечивающая вывод на экран параметров, снимаемых с датчика нового типа.• Модификация класса InputManager
, обеспечивающая обработку нажатия новой клавиши RainFall.• Правильное включение экземпляров класса RainFallSensor
в коллекцию Sensors.Нам может встретиться еще ряд более мелких задач по интеграции нового класса в уже существующую архитектуру, но в любом случае ни сама архитектура, ни основные механизмы системы не претерпят серьезных изменений.
Рассмотрим теперь совершенно другое функциональное свойство: предположим, что мы хотим обеспечить возможность пересылки собранных за день данных на удаленный компьютер. Для реализации этой задачи необходимо:
• Создание нового класса SerialPort
, ответственного за управление последовательным портом RS232.