// Число прошедших минут typedef unsigned int Minute;
Теперь опишем сам класс TemperatureRamp, который по смыслу задает функцию времени от температуры:
class TemperatureRamp { public:
TemperatureRamp(); virtual ~TemperatureRamp(); virtual void clear(); virtual void bind (Temperature, Minute); Temperature TemperatureAt (Minute);
protected: ... };
Выдерживая наш стиль, мы описали некоторые из операций виртуальными, так как ожидаем, что этот класс будет иметь подклассы.
На самом деле в смысле поведения нам надо нечто большее, чем просто зависимость температуры от времени. Пусть, например, известно, что на 60-й минуте должна быть достигнута температура 250╟F, а на 180-й - 150╟F. Спрашивается, какой она должна быть на 120-й минуте? Это требует линейной интерполяции, так что требуемое от абстракции поведение усложняется.
Вместе с тем, управления нагревателем, поддерживающего требуемый профиль, мы от этой абстракции не требуем. Мы предпочитаем разделение понятий, при котором нужное поведение достигается взаимодействием трех объектов: экземпляра TemperatureRamp, нагревателя и контроллера. Класс TemperatureController можно определить так:
class TemperatureController { public:
TemperatureController(Location); ~TemperatureController(); void process(const TemperatureRamp&); Minute schedule(const TemperatureRamp&) const;
private: ... };
Тип Location был определен в главе 2. Заметьте, что мы не ожидаем наследования от этого класса и поэтому не объявляем в нем никаких виртуальных функций.
Операция process обеспечивает основное поведение этой абстракции; ее назначение - передать график изменения температуры нагревателю, установленному в конкретном месте. Например, объявим:
TemperatureRamp growingRamp; TemperatureController rampController(7);
Теперь зададим пару точек и загрузим план в контроллер::
growingRamp.bind (250, 60); growingRamp.bind(150, 180); rampController.process(growingRamp);
В этом примере rampController - агент, ответственный за выполнение температурного плана, он использует объект growingRamp как сервер. Эта связь проявляется хотя бы в том, что rampController явно получает growingRamp в качестве параметра одной из своих операций.
Одно замечание по поводу нашего стиля. На первый взгляд может показаться, что наши абстракции - лишь объектные оболочки для элементов, полученных в результате обычной функциональной декомпозиции. Пример операции schedule показывает, что это не так. Объекты класса TemperatureController имеют достаточно интеллекта, чтобы определять расписание для конкретных профилей, и мы включаем эту операцию как дополнительное поведение нашей абстракции. В некоторых энергоемких технологиях (например, плавка металлов) можно существенно выиграть, если учитывать остывание установки и тепло, остающееся после предыдущей плавки. Поскольку существует операция schedule, клиент может запросить объект TemperatureController, чтобы тот рекомендовал оптимальный момент запуска следующего нагрева.
Видимость. Пусть есть два объекта А и в и связь между ними. Чтобы А мог послать сообщение в, надо, чтобы в был в каком-то смысле видим для А. Мы можем не заботиться об этом на стадии анализа, но когда дело доходит до реализации системы, мы должны обеспечить видимость связанных объектов.
В предыдущем примере объект rampController видит объект growingRamp, поскольку оба они объявлены в одной области видимости и потому, что growingRamp передается объекту rampController в качестве параметра. В принципе есть следующие четыре способа обеспечить видимость.
• Сервер глобален по отношению к клиенту.
• Сервер (или указатель на него) передан клиенту в качестве параметра операции.
• Сервер является частью клиента.
• Сервер локально порождается клиентом в ходе выполнения какой-либо операции.
Какой именно из этих способов выбрать - зависит от тактики проектирования.