質問

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 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.

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.

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

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top