Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Observer Pattern in C#

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?

like image 292
wootscootinboogie Avatar asked Oct 02 '22 04:10

wootscootinboogie


1 Answers

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();
}
like image 199
Jeff B Avatar answered Oct 23 '22 05:10

Jeff B