И только поверхностному наблюдателю может казаться, что пароксизмы довольства якобы прекратились. На самом деле они диалектически перешли в новое качество. Они, товарищи, распространились на сам процесс удовлетворения потребностей.До вчерашнего дня мне казалось, что тема паттернов, ненавязчиво затронутая в Паттерн "Стратегия", нашла свое логическое завершение в Паттерн "Стратегия": жаркое из питона. Похоже на то, что я заблуждался, ибо интерес к этой теме продолжал тлеть все это время и проявился в виде просьбы продолжить ее дальше.
(Аркадий и Борис Стругацкие "Понедельник начинается в субботу")
Один из моих читателей, интересующийся C++, вернее, пытающийся вглядываться в горизонты C++, опираясь на знания Java, предпринял самостоятельную попытку переписать на C++ код паттерна "Наблюдатель" из книги Head First Design Patterns By Eric Freeman, Elisabeth Robson, Bert Bates, Kathy Sierra, но в ходе этой работы у него возникли затруднения, которые мы сегодня постараемся разрешить.
Изучение C++ таким способом безусловно содержит в себе рациональное зерно и вполне может оказаться, что такой подход к изучению нового языка даст неплохие результаты.
Ну что же, давайте попробуем написать код паттерна "Наблюдатель" на C++, сохраняя, по возможности, некоторую общность с имеющейся реализацией на Java.
#include <iostream>
#include <vector>
#include <algorithm>
class Observer {
public:
virtual void update(float temp, float humidity, float pressure) = 0;
};
class Subject {
public:
virtual void registerObserver(Observer *obj) = 0;
virtual void removeObserver(Observer *obj) = 0;
virtual void notifyObservers() = 0;
};
class WeatherData: public Subject {
std::vector<Observer *> observers;
float temperature;
float humidity;
float pressure;
public:
void registerObserver(Observer *obj) {
observers.push_back(obj);
std::cout << "Observers: " << observers.size() << std::endl;
}
void removeObserver(Observer *obj) {
observers.erase(std::remove(observers.begin(), observers.end(), obj), observers.end());
}
void notifyObservers() {
std::vector<Observer *>::const_iterator cii;
for (cii=observers.begin(); cii!=observers.end(); cii++) {
(*cii)->update(temperature, humidity, pressure);
}
}
void measurementsChanged() { notifyObservers(); }
void setMeasurements(float temperature, float humidity, float pressure) {
this->temperature = temperature;
this->humidity = humidity;
this->pressure = pressure;
measurementsChanged();
}
float getTemperature() { return temperature; }
float getHumidity() { return humidity; }
float getPressure() { return pressure; }
};
class DisplayElement {
public:
virtual void display() = 0;
};
class CurrentConditionsDisplay: public Observer, DisplayElement {
float temperature;
float humidity;
Subject *weatherData;
public:
CurrentConditionsDisplay(Subject *weatherData) {
this->weatherData = weatherData;
weatherData->registerObserver(this);
}
void update(float temperature, float humidity, float pressure) {
this->temperature = temperature;
this->humidity = humidity;
display();
}
void display() {
std::cout << "Current conditions: " << temperature
<< "F degrees and " << humidity << "% humidity" << std::endl;
}
};
class StatisticsDisplay: public Observer, DisplayElement {
float maxTemp;
float minTemp;
float tempSum;
int numReadings;
WeatherData *weatherData;
public:
StatisticsDisplay(WeatherData *weatherData) {
this->weatherData = weatherData;
weatherData->registerObserver(this);
maxTemp = 0.0f;
minTemp = 200;
tempSum = 0.0f;
numReadings = 0;
}
void update(float temp, float humidity, float pressure) {
tempSum += temp;
numReadings++;
if (temp > maxTemp) { maxTemp = temp; }
if (temp < minTemp) { minTemp = temp; }
display();
}
void display() {
std::cout << "Avg/Max/Min temperature = " << (tempSum / numReadings)
<< "/" << maxTemp << "/" << minTemp << std::endl;
}
};
class ForecastDisplay: public Observer, DisplayElement {
float currentPressure;
float lastPressure;
WeatherData *weatherData;
public:
ForecastDisplay(WeatherData *weatherData) {
this->weatherData = weatherData;
weatherData->registerObserver(this);
currentPressure = 29.92f;
}
void update(float temp, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
void display() {
std::cout << "Forecast: " << std::endl;
if (currentPressure > lastPressure) {
std::cout << "Improving weather on the way!" << std::endl;
} else if (currentPressure == lastPressure) {
std::cout << "More of the same" << std::endl;
} else if (currentPressure < lastPressure) {
std::cout << "Watch out for cooler, rainy weather" << std::endl;
}
}
};
int main() {
WeatherData weatherData;
CurrentConditionsDisplay currentDisplay(&weatherData);
StatisticsDisplay statisticsDisplay(&weatherData);
ForecastDisplay forecastDisplay(&weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
$ g++ -Wall weather.cpp -o weather
$ ./weather.exe
Observers: 1
Observers: 2
Observers: 3
Current conditions: 80F degrees and 65% humidity
Avg/Max/Min temperature = 80/80/80
Forecast:
Improving weather on the way!
Current conditions: 82F degrees and 70% humidity
Avg/Max/Min temperature = 81/82/80
Forecast:
Watch out for cooler, rainy weather
Current conditions: 78F degrees and 90% humidity
Avg/Max/Min temperature = 80/82/78
Forecast:
More of the same
И под занавес замечу, что заслуживают более пристального внимания пара нюансов. А именно, тело метода
void removeObserver(Observer *obj)Здесь присутствует "Erase–remove idiom", на которую стоит обратить свой взор и ознакомиться с ней поближе. Тело метода
void notifyObservers()можно написать несколько иначе, если использовать C++ версии 11:
for (const auto& i: observers) { i->update(temperature, humidity, pressure); }
Команда вызова компилятора в этом случае:
g++ -Wall -std=c++11 weather.cpp -o weatherИ еще... Ну о "еще" поговорим в другой раз по мере появления новых вопросов.