Шаблон наблюдателя в C#
Я просматриваю (фантастическую) книгу Head First Design Patterns
и нуждаюсь в некоторых пояснениях относительно модели наблюдателя. Следующий фрагмент кода имитирует устройство (CurrentConditionDisplay), которое прослушивает обновления о погодных условиях.
Интерфейсы:
public interface ISubject
{
void RegisterObserver(IObserver obs);
void RemoveObserver(IObserver obs);
void NotifyObservers();
}
public interface IDisplay
{
string Display();
}
public interface IObserver
{
void Update(float temperature, float humidity, float pressure);
}
Наблюдатель
public class CurrentConditionDisplay : IObserver, IDisplay
{
private float temperature;
private float humidity;
private float pressure;
private ISubject weatherData;
public CurrentConditionDisplay(ISubject weatherData)
{
this.weatherData = weatherData;
this.weatherData.RegisterObserver(this);
}
public string Display()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("Welcome to Current Condition Display...");
sb.AppendLine(this.temperature.ToString());
sb.AppendLine(this.humidity.ToString());
sb.AppendLine(this.pressure.ToString());
return sb.ToString();
}
public void Update(float temperature, float humidity, float pressure)
{
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
}
}
Субъект
public class WeatherData : ISubject
{
private List<IObserver> observersList;
private float temperature;
private float humidity;
private float pressure;
public WeatherData()
{
observersList = new List<IObserver>();
}
public void RegisterObserver(IObserver obs)
{
observersList.Add(obs);
}
public void RemoveObserver(IObserver obs)
{
int index = observersList.IndexOf(obs);
if (index >= 0)
{
observersList.RemoveAt(index);
}
}
public void MeasurementsChanged()
{
Console.WriteLine("There is new data available...");
NotifyObservers();
}
public void NotifyObservers()
{
foreach (IObserver observer in observersList)
{
observer.Update(temperature, humidity, pressure);
}
}
public void SetMeasurements(float temperature, float humidity, float pressure)
{
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
MeasurementsChanged();
}
}
Использовать эти классы в моей программе.cs я создаю один раз instnce из WeatherData
и передаю этот объект в качестве параметра конструктору CurrentConditionDisplay
. Проблема, которую я вижу при этом текущая настройка заключается в том, что IObserver
имеет один метод Update
, который принимает temperature, humidity, pressure
в качестве параметров. Я не вижу никакой гарантии, что субъект (WeatherData) должен иметь эти поля в первую очередь. Должен ли я добавить другой интерфейс или абстрактный базовый класс, чтобы гарантировать, что при вызове SetMeasurements
все поля, обновляемые в этом методе, на самом деле находятся в Observer
?
4 ответа:
Я чувствую то же самое, что вы делаете... имея довольно общий звучащий интерфейс
IObserver
, есть специфическая сигнатура метода, которая на самом деле применяется только, когда наблюдениеWeatherData
чувствует себя отвратительно!Я бы предпочел что-то вроде этого:
public interface IObserver<T> { void Update(T updatedData); }
С наблюдателем, который выглядел бы примерно так (вырезал здесь дополнительный код):
public class CurrentConditionDisplay : IObserver<WeatherUpdate>, IDisplay { public CurrentConditionDisplay(ISubject<WeatherUpdate> weatherData) { this.weatherData = weatherData; this.weatherData.RegisterObserver(this); } public void Update(WeatherUpdate update) { this.temperature = update.Temperature; this.humidity = update.Humidity; this.pressure = update.Pressure; } }
И просто чтобы прояснить, мой общий
T
дляIObserver<T>
будет объектом, который инкапсулирует погоду обновление:public WeatherUpdate { public float Temperature; public float Humidity; public float Pressure; }
И
ISubject
придется изменить, чтобы включить также общий параметр:public interface ISubject<T> { void RegisterObserver(IObserver<T> obs); void RemoveObserver(IObserver<T> obs); void NotifyObservers(); }
Если вы хотите применить это, то вы можете определить свойства температуры, влажности и давления в интерфейсе ISubject (см. http://msdn.microsoft.com/en-us/library/64syzecx.aspx).
Затем измените метод Update в интерфейсе IObserver (и класс, который его реализует) - вы можете удалить параметры. Измените метод обновления класса CurrentConditionDisplay для поиска значений температуры, влажности, давления из свойств объекта, который реализует ISubject.
Нет,
Когда речь заходит об интерфейсах, важно помнить, что интерфейсы более тесно связаны с потребностями своих клиентов (то есть вызывающих устройств, в вашем случае классаIObserver
не должно быть полей для температуры, влажности и давления.WeatherData
), а не их реализаций.Я имею в виду, что вы должны смотреть на интерфейс с точки зрения потребностей класса
WeatherData
в первую очередь-этот класс использует интерфейсIObserver
для уведомления других об изменениях в температура, влажность и давление и ничего больше-ему не нужно получать температуру, влажность и давление от наблюдателей (что он будет делать с этой информацией?).На самом деле некоторые реализации
IObserver
могут даже не сохранять эту информацию - например, какой-то наблюдатель протоколирования может регистрировать эти изменения, а затем полностью отбросить эту информацию. В этом случае добавление этих свойств к интерфейсу заставит наблюдателя реализовать элементы что ни наблюдатель, ни реализация на самом деле не нужны!При определении интерфейсов всегда думайте в терминах методов и свойств, которые должен использовать вызывающий объект - все остальное является деталями реализации класса, реализующего интерфейс.
Ваше право, при текущей реализации нет никакой гарантии, что наблюдатель обладает свойствами температуры, влажности и давления. Это не имеет значения, так как то, что является гарантировано, что вы получите эту информацию с помощью метода update вызывается.
ДОПОЛНИТЕЛЬНОЕ ЧТЕНИЕ
Для ясности рассмотрим пример реального мира :
DoFactory.com: Модель Наблюдателя
Еще один великий ресурс: