последовательность считывания, обработки и вывода на экран значений параметров через определенные промежутки времени. Каждый элемент такой последовательности называется
На рис. 8-13 приведена диаграмма классов, отражающая особенности архитектуры системы. Здесь присутствуют, в основном, те же самые классы, которые были определены на этапе анализа. Главное отличие от предыдущих диаграмм состоит в том, что теперь мы видим, каким образом ключевые абстракции нашего программного приложения взаимодействуют друг с другом. Мы, естественно, не можем отразить на одной диаграмме все существующие классы и связи между ними. Здесь, например, не воспроизведена иерархия классов-датчиков.
Кроме того, мы ввели один новый класс Sensors, который служит для объединения в коллекцию всех объектов-датчиков. Поскольку по крайней мере два агента (Sampler и InputManager) в нашей системе должны ассоциироваться с целой коллекцией датчиков, помещение их в один контейнерный класс позволяет рассматривать все датчики единым образом.
Механизм покадровой обработки
Поведение нашей системы в основном определяется взаимодействием классов Sampler и Timer, поэтому, чтобы оправдать нашу модель, следует быть особенно внимательным при их описании.
Начнем с разработки внешнего интерфейса для класса Timer, осуществляющего диспетчеризацию функции обратного вызова (все решения будут в дальнейшем реализовываться на языке C++). Во-первых, с помощью ключевого слова typedef определим новый тип переменной, Tick, соответствующий словарю нашей проблемной области.
// Временной промежуток, измеряемый в 1/60 долях секунды typedef unsigned int Tick
Затем определим класс Timer:
class Timer { public:
static setCallback(void (*)(Tick)); static startTiming(); static Tick numberOfTicks();
private: ... };
Это - необычный класс хотя бы потому, что он содержит не совсем обычную информацию. Функция- член setCallback используется для передачи таймеру функции обратного вызова. Таймер запускается вызовом функции startTiming, после чего единственный экземпляр класса Timer начинает вызывать функцию обратного вызова каждую 1/60 секунды. Отметим, что функция запуска введена в явном виде, поскольку нельзя полагаться на то, как в частной реализации определяется порядок обработки объявлений.
Прежде чем перейти к классу Sampler, желательно ввести перечислимый тип всех датчиков, присутствующих в нашей системе, следующим образом:
// Перечисление названий датчиков enum SensorName {Direction, Speed, WindChill, Temperature, DewPoint, Humidity, Pressure};
Теперь можно определить интерфейс класса Sampler:
class Sampler { public:
Sampler(); ~Sampler(); void setSamplingRate(SensorName, Tick); void sample(Tick); Tick samplingRate() const;
protected: ... };
Для того, чтобы клиент мог динамически изменять поведение сэмплера, мы определили модификатор setSamplingRate и селектор samplingRate.
Чтобы обеспечить связь между классами Timer и Sampler, придется еще приложить небольшие усилия. В следующем фрагменте кода создается объект класса Sampler и определяется 'неклассовая' функция acquire:
Sampler sampler;
void acquire(Tick t) {
sampler.sample(t);
}
После этого можно написать функцию main, где просто происходит присоединение к таймеру функции обратного вызова и запускается процесс опроса датчиков:
main() {
Timer::setCallback(acquire); Timer::startTiming(); while (1); return 0;
}
Это довольно типичная для объектно-ориентированной системы главная функция: она короткая (потому что основная работа делегирована объектам) и включает в себя цикл диспетчеризации (в нашем случае пустой, так как отсутствуют какие-либо фоновые процессы).
Продолжим рассмотрение нашей задачи. Определим теперь внешний интерфейс класса Sensors (датчики). Мы предполагаем, что существуют различные конкретные классы датчиков:
class Sensors : protected Collection { public:
Sensors(); virtual ~Sensors(); void addSensor(const Sensor& SensorName, unsigned int id = 0); unsigned int numberOfSensors() const; unsigned int numberOfSensors(SensorName); Sensor& sensor(SensorName, unsigned int id = 0);
protected: };
Это, в основном, класс-коллекция и поэтому он объявляется подклассом фундаментального класса Collection. Класс Collection указан как защищенный суперкласс; это сделано для того, чтобы скрыть детали его строения от клиентов класса Sensor. Обратите внимание на то, что набор операций, который мы определили для класса Sensors, крайне скуден - это вызвано ограниченностью задач класса. Мы, например, знаем, что датчики могут добавляться в коллекцию, но не удаляться из нее.
Таким образом, мы изобрели класс-коллекцию для датчиков, который может содержать множество экземпляров датчиков одного и того же типа, причем каждый экземпляр своего класса имеет уникальный идентификационный номер, начиная с нуля.
Вернемся к спецификации класса Sampler. Нам надо обеспечить его ассоциацию с классами Sensors и DisplayManager:
class Sampler { public:
Sampler(Sensors&, DisplayManager&) ;
protected:
Sensors& repSensors; DisplayManager& repDisplayManager;
};
Теперь следует изменить фрагмент кода, где происходит создание экземпляра класса Sampler:
Sensors sensors; DisplayManager display; Sampler sampler (sensors, display);
При порождении объекта Sampler устанавливается связь между ним, коллекцией