C#中的观察者模式

5
我是一个有用的助手,可以翻译文本。
我正在阅读《Head First 设计模式》这本(神奇的)书,并需要对观察者模式进行一些澄清。以下代码片段模拟了一个设备(CurrentConditionDisplay),该设备侦听天气模式的更新。
接口:
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);
}

观察者模式
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;
    }
}

主题

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();
    }
}

为了在 Program.cs 中使用这些类,我正在创建一个 WeatherData 实例,并将该对象作为参数传递给 CurrentConditionDisplay 构造函数。 我发现当前设置的一个问题是 IObserver 只有一个方法 Update,该方法以温度、湿度和压力作为参数。 我没有看到 Subject (WeatherData) 必须首先拥有这些字段的保证。 我应该添加另一个接口或抽象基类,以确保当调用 SetMeasurements 时,所有在该方法中更新的字段实际上都在 Observer 中吗?

7
C#内置了观察者模式的实现。请参阅Events - John Saunders
2
@JohnSaunders 这是一本关于设计模式的书。目标是理解观察者模式的思想,而不是学习C#的特性。 - Sergey Berezovskiy
@lazyberezovsky 我的意思是WeatherData没有将temperature, humidity, pressure作为私有字段。观察者和主题没有共同的接口(来共享那些被更新的字段)。 - wootscootinboogie
@wootscootinboogie,我又听不懂你说什么了——温度、湿度和气压是天气数据的私有字段。你为什么说没有任何东西使它们成为私有字段呢? - Sergey Berezovskiy
@lazyberezovsky WeatherDataCurrentConditionDisplay都有温度、湿度和压力作为它们之间共同的私有字段。如果我从WeatherData调用CurrentConditionDisplay的Update方法,我是否需要一个基类或接口来说“嘿,我保证你更新的是你想要的内容?” - wootscootinboogie
显示剩余5条评论
4个回答

2

我和你有同感...一个听起来非常通用的 IObserver 接口,但是它具体的方法签名只适用于观察 WeatherData 的时候,这让人感到不舒服!

我更喜欢像这样的东西:

public interface IObserver<T>
{
    void Update(T updatedData);
}

使用观察者模式,代码如下(省略了一些其他代码):

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;
    }
}

为了让自己清楚,我的泛型 T 对于 IObserver<T> 将是一个封装天气更新的对象:

public WeatherUpdate
{
    public float Temperature;
    public float Humidity;
    public float Pressure;
}

而且ISubject也必须进行更改,以包括泛型参数:

public interface ISubject<T>
{
    void RegisterObserver(IObserver<T> obs);
    void RemoveObserver(IObserver<T> obs);
    void NotifyObservers();
}

我认为这也是一个好主意。如果每个设备有几种不同类型的更新,那么这将轻松地允许CurrentDisplayUpdateStatsDisplayUpdate等更新。 - wootscootinboogie

1
如果你想要强制实现这个功能,你可以在ISubject接口中定义temp、humidity和pressure的属性(参见http://msdn.microsoft.com/en-us/library/64syzecx.aspx)。然后调整IObserver接口(以及实现它的类)中的Update方法 -- 可以删除参数。将CurrentConditionDisplay类的Update方法更改为从实现ISubject接口的对象的属性中查找temp、humidity和pressure值。

1
不,IObserver 不需要温度、湿度和气压字段。
当涉及到接口时,重要的是记住,接口与其客户端(即调用者,在您的情况下为 WeatherData 类)的需求更密切相关,而不是它们的实现。
我的意思是,您应该首先从 WeatherData 类的需求角度查看接口——这个类使用 IObserver 接口来通知其他人温度、湿度和气压的变化,而且仅此而已。它不需要从观察者那里获得温度、湿度和气压(它会用这些信息做什么呢?)。
实际上, IObserver 的某些实现甚至可能不会保留这些信息——例如,某种日志观察者可能会记录这些更改,然后完全丢弃这些信息。在这种情况下,将这些属性添加到接口中将迫使观察者实现观察者或实现实际上都不需要的成员!
定义接口时,始终考虑调用者需要使用的方法和属性——其他所有内容都是实现接口的类的实现细节。

作为一种设计选择,您是否同意或不同意实现观察者和主题都实现的新接口或抽象类,该接口或抽象类具有要更新的属性/字段? - wootscootinboogie
@wootscootinboogie 很好的问题,但是弄清楚类应该何时继承彼此是一个不同(更复杂)的主题。在这种情况下,我会说不需要公共基类或接口(一般来说,您不应该添加基类型,只因为它们具有相同的成员)。请参阅Liskov替换原则 - Justin
直觉上,我本来会猜想他们需要一个共同的抽象,因为他们确实有共同的成员。这个小事实将为我节省一些麻烦。 - wootscootinboogie

0

你说得对,目前的实现不能保证观察者具有温度、湿度和气压属性。不过这无关紧要,确保的是当更新方法被调用时,你将收到这些信息。

更多阅读

为了更清晰,可以看一下现实世界的例子:

DoFactory.com:观察者模式

另一个很好的资源:

PluralSight.com:设计模式库


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接