Теперь можно определить интерфейс класса 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
устанавливается связь между ним, коллекцией датчиков sensors, и экземпляром класса DisplayManager, который будет использоваться системой.Теперь можно заняться описанием ключевой операции класса Sampler
, а именно, sample:void Sampler::sample(Tick t)
{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).currentValue(), name, id);
}
Эта функция по очереди опрашивает каждый тип датчика и каждый датчик внутри типа. Она проверяет, пришло ли время считывать информацию с датчика, и если да, то определяет ссылку на датчик в коллекции, считывает его текущее значение и передает его менеджеру дисплея, ассоциированному с данным экземпляром класса Sampler
.Семантика этой операции основывается на полиморфном поведении определенного метода, а именно:
virtual float currentValue();
определенного для базового класса sensor. Эта операция, кроме того, основывается на функции display класса DisplayManager
: