Head First设计模式02 观察者模式

"设计模式-观察者模式"

Posted by 小镇青年 on January 23, 2019

本文为HeadFirst读书笔记

认识观察者模式

报社是怎么运作的?

  1. 报社出版报纸
  2. 用户向 某家报社订阅报纸,只要报社有新报纸出版了,就会给用户送过来。只要用户是报社的订户,用户就会一直收到新报纸。
  3. 当用户不想再看报纸的时候,取消订阅,报社就不再送新报纸给用户。
  4. 只要报社还在运营,就会一直有用户向他们订阅报纸,也一直有用户取消订阅。

定义观察者模式

出版者+订阅者=观察者

当你试图勾勒观察者模式时,可以利用报纸订阅服务来比拟。我们将出版者称为主题(Subject),订阅者称为观察者(Observer)

观察者模式定义了对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖者都会收到通知并自动更新。观察者模式提供了一种对象设计,让主题和观察者之间松耦合,当两个对象松耦合后,它们依然可以交互,但是不太清楚彼此的细节。

设计原则

为了交互对象之间的松耦设计合而努力。

实现一个气象站

接口定义

主题接口

每一个主题可以有多个观察者 定义一个主题接口,包含 注册观察者删除观察者通知观察者 功能。其中注册及删除观察者需要有一个观察者作为入参,以表示该观察者被注册或删除。当主题状态改变的时候,NodifyObserver()方法会被调用,以通知所有观察者。

1
2
3
4
5
6
public interface ISubject
{
    void RegisterObserver(IObserver observer);
    void RemoveObserver(IObserver observer);
    void NodifyObserver();
}

观察者接口

所有观察者接口都实现此接口。这样,当主题在需要通知观察者的时候,有了一个共同的接口。 当气象观测值发生改变时,Update()方法中的参数会被传递给观察者。

1
2
3
4
public interface IObserver
{
    void Update(float temp, float humidity, float pressure);
}

显示元素接口

不同的布告板需要显示不同的天气元素,所以我们定义一个显示天气元素的接口

1
2
3
4
public interface IDisplayElement
{
    void Display();
}

实现

气象数据类实现

定义一个气象数据类,实现主题接口。其中,我们定义了一个观察者集合来记录观察者,当注册观察者时,我们添加到此集合中,当删除观察者时,我们从集合中移除该观察者。当气象站得到更新气象数据时,我们通知(NodifyObserver())观察者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class WeatherData : ISubject
{
    private List<IObserver> observers;
    private float temperature;
    private float humidity;
    private float pressure;
    public WeatherData()
    {
        observers = new List<IObserver>();
    }
    public void RegisterObserver(IObserver observer)
    {
        observers.Add(observer);
    }
    public void RemoveObserver(IObserver observer)
    {
        observers.Remove(observer);
    }
    public void NodifyObserver()
    {
        foreach (var observer in observers)
        {
            observer.Update(temperature, humidity, pressure);
        }
    }
    public void MeasurementsChanged()
    {
        NodifyObserver();
    }
    public void SetMeasurements(float temperature, float humidity, float pressure)
    {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        MeasurementsChanged();
    }
}

布告板建立

建立布告板,以展示天气数据,比如布告板A仅需要展示气温,布告板需要展示气温及湿度。 以下代码演示了布告板B的实现代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TackBoardB : IObserver, IDisplayElement
{
    private float temperature;
    private float humidity;
    private ISubject _weatherData;

    public TackBoardB(ISubject weatherData)
    {
        _weatherData = weatherData;
        _weatherData.RegisterObserver(this);
    }

    public void Display()
    {
        Console.WriteLine($"布告板B:{temperature}℃ and {humidity}% humidity");
    }

    public void Update(float temperature, float humidity, float pressure)
    {
        this.temperature = temperature;
        this.humidity = humidity;
        Display();
    }
}

测试气象站程序

1
2
3
4
5
6
7
8
static void Main(string[] args)
{
    WeatherData weatherData = new WeatherData();
    TackBoardB currentConditionsDisplay = new TackBoardB(weatherData);
    weatherData.SetMeasurements(10, 50, 30.2f);
    weatherData.SetMeasurements(5, 45, 31.2f);
    weatherData.SetMeasurements(-1, 48, 32.2f);
}

输出结果如下

1
2
3
4
5
6
布告板A:10℃
布告板B:10℃ and 50% humidity
布告板A:5℃
布告板B:5℃ and 45% humidity
布告板A:-1℃
布告板B:-1℃ and 48% humidity

源码: GitHub