简体   繁体   English

C#中的观察者模式

[英]Observer Pattern in C#

I'm going through the (fantastic) book Head First Design Patterns and need some clarification on the observer pattern. 我正在阅读(奇妙的) Head First Design Patterns一书,需要对观察者模式进行一些澄清。 The following little bit of code simulates a device (CurrentConditionDisplay) that listens for updates on weather patterns. 以下一点代码模拟了一个侦听天气模式更新的设备(CurrentConditionDisplay)。

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 . 要在我的Program.cs中使用这些类,我将创建一次WeatherData并将该对象作为参数传递给CurrentConditionDisplay的构造函数。 A problem that I see with this current setup is that IObserver has one method Update which takes temperature, humidity, pressure as parameters. 我在这个当前设置中看到的一个问题是IObserver有一个方法Update ,它以temperature, humidity, pressure为参数。 I see no guarantee that the Subject (WeatherData) has to have these fields in the first place. 我看不出主题(WeatherData)必须首先包含这些字段。 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 ? 我应该添加另一个接口还是抽象基类,以确保在SetMeasurements时,该方法中正在更新的所有字段实际上都在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! 我觉得你做同样的事情......拥有一个相当通用的声音IObserver接口有一个特定的方法签名,真正适用于观察WeatherData感觉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: 为了使自己清楚,我的IObserver<T>通用T将是一个封装天气更新的对象:

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: 并且必须更改ISubject以包含通用参数:

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 ). 如果您想强制执行此操作,您可以在ISubject界面中定义温度,湿度和压力的属性(请参阅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. 然后在IObserver接口(以及实现它的类)中调整Update方法 - 您可以删除参数。 Change the CurrentConditionDisplay class' Update method to look for the temp, humidity, pressure values from the properties of the object that implements ISubject. 更改CurrentConditionDisplay类的Update方法,以从实现ISubject的对象的属性中查找temp,humidity,pressure值。

No, IObserver doesn't to have fields for temperature, humidity and pressure. 不, IObserver没有温度,湿度和压力的字段。

When it comes to interfaces its important to remember that interfaces are more closely tied to the needs of their clients (ie the callers, in your case the WeatherData class) rather than their implementations. 说到界面,重要的是要记住界面与客户的需求(即调用者,在你的情况下是WeatherData类)而不是他们的实现更紧密地联系在一起。

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?). 我的意思是,你应该看看从需求的角度接口WeatherData类第一-这个类使用IObserver接口通知其他变化的温度,湿度和压力,仅此而已-它不需要从观察者那里获得温度,湿度和压力(这些信息会怎么做?)。

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. 事实上, IObserver某些实现可能甚至不会持久保存此信息 - 例如,某种日志记录观察器可能会记录这些更改,然后完全丢弃此信息。 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 DoFactory.com:观察者模式

Another great resource: 另一个很棒的资源

PluralSight.com: Design Patterns Library PluralSight.com:设计模式库

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM