Reputation: 8695
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
?
Upvotes: 5
Views: 2196
Reputation: 8982
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();
}
Upvotes: 2
Reputation: 86729
No, IObserver
doesn't to have fields for temperature, humidity and pressure.
When it comes to interfaces its important to remember that interfaces are more closely tied to the needs of their clients (i.e. the callers, in your case the WeatherData
class) rather than their implementations.
What I mean by this is that you should look at the interface from the perspective of the needs of WeatherData
class first - this class uses the IObserver
interface to notify others of changes to temperature, humidity and pressure and nothing more - it doesn't need to get the temperature, humidity and pressure from the observers (what would it do with this information?).
In fact some implementations of IObserver
might not even persist this information - for example some sort of logging observer might log these changes and then completely discard this information. In this case adding these properties to the interface would force the observer into implement members that nether the observer nor the implementation actually needs!
When defining interfaces always think in terms of the methods and properties that the caller needs to use - everything else is implementation detail of the class that implements the interface.
Upvotes: 1
Reputation: 2875
Your right, with the current implementation there is no guarantee that the observer has the temperature, humidity and pressure properties. It doesn't matter as what is guaranteed is that you will receive this information with the update method is called.
ADDITIONAL READING
For clarity, consider taking a look at the real-world example:
DoFactory.com: Observer Pattern
Another great resource:
PluralSight.com: Design Patterns Library
Upvotes: 0
Reputation: 5160
If you wanted to enforce this, what you could do is define the properties for temp, humidity and pressure in your ISubject interface (see http://msdn.microsoft.com/en-us/library/64syzecx.aspx).
Then adjust the Update method in your IObserver interface (and the class that implements it) -- you can remove the parameters. Change the CurrentConditionDisplay class' Update method to look for the temp, humidity, pressure values from the properties of the object that implements ISubject.
Upvotes: 1