Механизм датчиков
Мы уже видели, как при разработке архитектуры системы постепенно наполнялись содержанием и приобретали устойчивые формы ее ключевые абстракции, в том числе классы датчиков. Руководствуясь эволюционным подходом к разработке, будем строить следующую версию на основе первой, минимальной.
На данном этапе разработки иерархия классов-датчиков, представленная на рис. 8-4, остается без изменений. Мы, однако, должны уточнить местонахождение некоторых полиморфных операций, чтобы добиться как можно более высокой степени общности классов в иерархии. Ранее, например, мы описали требования к операции currentValue, принадлежащей абстрактному базовому классу Sensor. Более полно конструкцию данного класса можно определить на C++ следующим образом:
class Sensor {
public:
Sensor(SensorName, unsigned int id = 0);
virtual ~Sensor();
virtual float currentValue = 0;
virtual float rawValue() = 0;
SensorName name() const;
unsigned int id() const;
protected:
...
};
Этот класс включает в себя чисто виртуальные функции-члены, и поэтому является абстрактным.
Отметим, что конструктор класса сообщает экземпляру его имя и номер. Это сделано для обеспечения возможности динамического определения типа датчика, а также для того, чтобы удовлетворить одно из требований к системе, согласно которому каждый из датчиков имеет постоянный адрес доступа в оперативной памяти. Эти детали реализации системы можно скрыть, вычисляя адрес в памяти через тип датчика и его идентификационный номер.
После того, как мы добавили новые свойства к классу датчиков, можно вернуться немного назад и упростить объявление функции DisplayManager::display, которая теперь может иметь только один аргумент, а именно ссылку на объект класса Sensor. От остальных аргументов можно отказаться, так как объект класса, производного от sensor, сам выдаст информацию о своем типе и идентификационном номере.
Это казалось бы незначительное изменение крайне желательно, так как если не стремиться к упрощению внешнего интерфейса классов, то со временем наша система будет все больше и больше страдать от перегруженности протоколов взаимодействия между ними.
Объявление подкласса CalibratingSensor основывается на базовом классе Sensor:
class CalibratingSensor : public Sensor {
public:
CalibratingSensor(SensorName, unsigned int id = 0);
virtual ~CalibratingSensor();
void setHighValue(float, float);
void setLowValue(float, float);
virtual float currentValue();
virtual float rawValue() = 0;
protected:
...
};
Этот класс включает в себя две новые операции (setHighValue и setbowValue), и реализует виртуальную функцию currentValue базового класса.
Теперь рассмотрим объявление подкласса HistoricalSensor, базирующегося на классе CalibratingSensor:
class HistoricalSensor : public CalibratingSensor {
public:
HistoricalSensor(SensorName, unsigned int id = 0);
virtual ~HistoricalSensor();
float highValue() const;
float lowValue() const;
const char* timeOfHighValue() const;
const char* timeOfLowValue() const;
protected:
...
};
В этом классе определены четыре новые операции, реализация которых требует взаимодействия с классом TimeDate. Отметим также, что HistoricalSensor все еще является абстрактным классом, так как мы не определили в нем реализацию чисто виртуальной функции rawValue, которая будет определена в следующем подклассе.
Класс TrendSensor является производным от HistoricalSensor; в нем добавлено одно новое свойство:
class TrendSensor : public HistoricalSensor {
public:
TrendSensor(SensorName, unsigned int id = 0);
virtual ~TrendSensor();
float trend() const;
protected:
...
};
В этом классе определена одна новая функция trend. Как и некоторые другие операции, добавляемые в промежуточные классы, она не обозначена как виртуальная, так как мы не хотим, чтобы наследующие классы ее переопределяли.
И вот, наконец, мы переходим к конкретному классу TemperatureSensor:
class TemperatureSensor : public TrendSensor {
public:
TemperatureSensor(unsigned int id = 0);
virtual ~TemperatureSensor();
virtual float rawValue();
float currentTenperature();
protected:
...
};
Отметим, что сигнатура конструктора для этого класса определена по-новому.Здесь нам известен конкретный тип датчика, поэтому нет необходимости задавать его имя при создании объекта. Обратим также внимание на новую операцию currentTemperature. Ее присутствие логически вполне оправдано, однако, если мы вернемся к результатам нашего анализа, то обнаружим, что аналогичную операцию выполняет полиморфная функция currentValue. Тем не менее, мы включили в описание и ту, и другую функции, так как операция currentTemperature более безопасна с точки зрения типов.
После того, как мы успешно завершили реализацию всех классов данной иерархии и интегрировали их с предыдущим релизом, можно переходить к следующему уровню функциональности системы.