【问题标题】:Observer Pattern in C#C#中的观察者模式
【发布时间】:2013-12-30 23:37:47
【问题描述】:

我正在阅读(很棒的)书Head First Design Patterns,需要对观察者模式进行一些澄清。下面的一小段代码模拟了一个设备 (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,它将temperature, humidity, pressure 作为参数。我认为不能保证主题(WeatherData)必须首先拥有这些字段。我是否应该添加另一个接口或抽象基类以确保在调用SetMeasurements时,该方法中正在更新的所有字段实际上都在Observer中?

【问题讨论】:

  • 仅供参考,C# 内置了观察者模式的实现。见Events
  • 你说的'是什么意思'确保在那个方法中被更新的所有字段实际上都在观察者中'?读了好几遍了,还是不明白你的问题
  • @JohnSaunders 那是一本关于设计模式的书。目标是理解观察者的想法,而不是学习 C# 特性)
  • @lazyberezovsky 我的意思是没有什么让WeatherData 拥有temperature, humidity, pressure 作为私有字段。 Observer 和 Subject 没有通用接口(共享那些更新的字段)。
  • @wootscootinboogie 我又听不懂你了——温度、湿度和压力是天气数据的私有字段。为什么你说没有把它们作为私有领域?

标签: c# design-patterns observer-pattern


【解决方案1】:

您说得对,目前的实现无法保证观察者具有温度、湿度和压力属性。 保证你会在调用 update 方法时收到此信息并不重要。

补充阅读

为清楚起见,请考虑查看 real-world 示例:

DoFactory.com: Observer Pattern

另一个很棒的资源:

PluralSight.com: Design Patterns Library

【讨论】:

    【解决方案2】:

    如果您想强制执行此操作,您可以在 ISubject 界面中定义温度、湿度和压力属性(请参阅http://msdn.microsoft.com/en-us/library/64syzecx.aspx)。

    然后调整 IObserver 接口(以及实现它的类)中的 Update 方法——您可以删除参数。更改 CurrentConditionDisplay 类的 Update 方法,从实现 ISubject 的对象的属性中查找温度、湿度、压力值。

    【讨论】:

      【解决方案3】:

      不,IObserver 没有温度、湿度和压力字段。

      当谈到接口时,重要的是要记住接口与其客户的需求(即调用者,在您的情况下为 WeatherData 类)的需求更紧密地联系在一起,而不是他们的实现。

      我的意思是,你应该先从WeatherData类的需求角度来看接口——这个类使用IObserver接口来通知其他人温度、湿度和压力的变化,什么都没有更多 - 它不需要从观察者那里获取温度、湿度和压力(它将如何处理这些信息?)。

      事实上,IObserver 的某些实现可能甚至不会保留此信息 - 例如,某种日志观察器可能会记录这些更改,然后完全丢弃此信息。在这种情况下,将这些属性添加到接口将迫使观察者成为观察者和实现都不需要的实现成员!

      在定义接口时,始终考虑调用者需要使用的方法和属性 - 其他一切都是实现接口的类的实现细节。

      【讨论】:

      • 谢谢:)。因此,作为设计选择,您会同意还是不同意观察者和主体都实现的具有要更新的属性/字段的新接口或抽象类?
      • @wootscootinboogie 好问题,但弄清楚类何时应该相互继承是一个不同(更复杂)的主题。在这种情况下,我会说不,它们不需要通用的基类或接口(通常,您不应该仅仅因为它们具有相同的成员而添加基类型)。见Liskov Substitution Principle
      • 直觉上我猜想他们需要一个共同的抽象,因为他们确实有共同的成员。那个小事实会让我头疼。
      【解决方案4】:

      我和你有同样的感觉......听起来相当通用的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 for IObserver&lt;T&gt; 将是一个封装天气更新的对象:

      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 或者你有什么。
      猜你喜欢
      • 2021-11-22
      • 2016-02-20
      • 2023-04-10
      • 1970-01-01
      • 2014-05-26
      • 1970-01-01
      • 2012-02-15
      • 2011-05-15
      • 1970-01-01
      相关资源
      最近更新 更多