5

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?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
wootscootinboogie
  • 8,461
  • 33
  • 112
  • 197
  • 7
    FYI, C# has a built-in implementation of the observer pattern. See [Events](http://msdn.microsoft.com/en-us/library/awbftdfh.aspx) – John Saunders Dec 12 '13 at 14:02
  • What do you mean by *'ensure that all the fields being updated in that method are actually in the Observer'*? Have read this several times and can't understand your problem – Sergey Berezovskiy Dec 12 '13 at 14:02
  • 2
    @JohnSaunders that's a book about design patterns. Goal is to understand observer idea, not to learn C# features) – Sergey Berezovskiy Dec 12 '13 at 14:03
  • @lazyberezovsky I meant that there's nothing making `WeatherData` have `temperature, humidity, pressure` as private fields. The Observer and Subject have no common interface (to share those fields that are udpated). – wootscootinboogie Dec 12 '13 at 14:05
  • @wootscootinboogie I can't understand you again - temperature, humidity and pressure are private fields of weather data. Why you are saying that nothing making them as private fields? – Sergey Berezovskiy Dec 12 '13 at 14:06
  • @lazyberezovsky `WeatherData` and `CurrentConditionDisplay` both have temperature, humidity and pressure as common private fields between them. If I'm calling the Update method on CurrentConditionDisplay from WeatherData, do I not need a base class or interface to say `Hey, I guarantee what you're updating is what you intended? – wootscootinboogie Dec 12 '13 at 14:10
  • It seems to me that I could pass any three parameters from WeatherData (maybe wind speed, snow fall, etc) and the observer could be getting the wrong data. – wootscootinboogie Dec 12 '13 at 14:11
  • @wootscootinboogie it's not necessary to hold temp, humidity and pressure in observer - it just gets notification when something happens in subject. You can hold those variable for example to compare what changed. But that's not required – Sergey Berezovskiy Dec 12 '13 at 14:12
  • @wootscootinboogie of course you can pass anything. You can even throw exception. But why would subject notify observers with wrong data? – Sergey Berezovskiy Dec 12 '13 at 14:13
  • @lazyberezovsky I thought that it might be a better design to have an interface or base class `UpdatableData` where temperature, humidity, etc. live so that we could be sure the right data was being passed. – wootscootinboogie Dec 12 '13 at 14:14

4 Answers4

2

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();
}
Jeff B
  • 8,572
  • 17
  • 61
  • 140
  • I think that is a good idea as well. If there were several different types of updates per device this would easily allow for `CurrentDisplayUpdate` or `StatsDisplayUpdate` or what have you. – wootscootinboogie Dec 13 '13 at 15:15
1

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.

mikey
  • 5,090
  • 3
  • 24
  • 27
1

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.

Justin
  • 84,773
  • 49
  • 224
  • 367
  • thanks :). So as a design choice would you agree or disagree with a new interface or abstract class that the observer and subject both implement that has the properties/fields to be updated? – wootscootinboogie Dec 12 '13 at 14:31
  • @wootscootinboogie Good question, but figuring out when classes should inherit from each other is a different (more complicated) subject. In this case I'd say no, they don't need a common base class or interface (in general you shouldn't add base types just because they have the same members). See the [Liskov Substitution Principle](http://stackoverflow.com/questions/56860/what-is-the-liskov-substitution-principle) – Justin Dec 12 '13 at 14:38
  • intuitively I would've guessed that they would need a common abstraction expressly because they DID have common members. That little factoid is going to save me some headache. – wootscootinboogie Dec 12 '13 at 14:40
0

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

Pressacco
  • 2,815
  • 2
  • 26
  • 45