I'm going through the (fantastic) book Head First Design Patterns
and need some clarification on the observer pattern. The following little bit of code simulates a device (CurrentConditionDisplay) that listens for updates on weather patterns.
interfaces:
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);
}
Observer
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;
}
}
Subject
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();
}
}
To use these classes in my Program.cs I'm creating once instnce of WeatherData
and passing that object as parameter to the constructor of CurrentConditionDisplay
. A problem that I see with this current setup is that IObserver
has one method Update
which takes temperature, humidity, pressure
as parameters. I see no guarantee that the Subject (WeatherData) has to have these fields in the first place. Should I add another interface or abstract base class to ensure that when SetMeasurements
is called, all the fields being updated in that method are actually in the Observer
?
I feel the same thing you do... having a rather generic sounding IObserver
interface have a specific method signature that really only applies when observing WeatherData
feels icky!
I'd much rather have something like this:
public interface IObserver<T>
{
void Update(T updatedData);
}
With an observer that would look something like this (snipped some extra code here):
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;
}
}
And just to make myself clear, my generic T
for IObserver<T>
would be an object that encapsulates a weather update:
public WeatherUpdate
{
public float Temperature;
public float Humidity;
public float Pressure;
}
And ISubject
would have to be changed to include the generic parameter as well:
public interface ISubject<T>
{
void RegisterObserver(IObserver<T> obs);
void RemoveObserver(IObserver<T> obs);
void NotifyObservers();
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With