设计模式--观察者模式 Observer Pattern

2019-10-21  本文已影响0人  慢慢1111

本文主要是记录《Head First 设计模式》知识,目的是检查自己学到的知识,同时方便我以后进行复习和浏览。

一、概述

1-1 定义

观察者模式 Observer Pattern:Define a one-to-many dependency between object so that when one object changes state,all its dependents are notified and updated automatically.
定义对象间的一种一对多的依赖关系,每当一个对象的状态发生改变时,所有依赖于它的对象都可以得到通知并被自动更新。也称为发布-订阅(Publish/Subscribe)模式、模型-视图(Model-View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式

该模式是一种行为模式

1-2 模式结构

观察者模式包含如下角色:

图1 模式结构

1-3 模式动机

建立一种对象与对象之间的依赖,当一个对象发生改变时,会自动通知其依赖于该对象的所有对象,这些对象会作出相应反应。其中改变的对象称为主体,而被通知的对象称为观察者,一个观察目标(观察者接口)对应多个观察者,观察者之间没有互相联系,观察者本身可以根据需要决定是否注册和删除,使系统更易于扩展(具体观察者与具体目标/主题)。

二、举例:

该观察者模式存在两种情况:

2-1 应用概述

气象监控应用:由WeatherData对象负责追踪目前的天气状况(温度、湿度、气压)。建立一种应用,有三种布告板,分别为目前的状态、气象统计及简单的预报。当WeatherData对象获取到最新值时,三种布告板必须实时更新。Weather-O-Rama希望公布一组API,好让其他开发人员可以写出自己的布告板,并插入此应用中。

图2

这是气象台送来的WeatherData源文件:


图3 WeatherData类的UML图

目前我们知道些什么:

2-2 推模式

图4 观察者推模式UML

推模式代码
主题代码:

/// <summary>
/// 主题接口
/// </summary>
public interface ISubject
{
    /// <summary>
    /// 注册观察者
    /// </summary>
    /// <param name="O"></param>
    public void RegisterObserver(IObserver O);
    /// <summary>
    /// 删除观察者
    /// </summary>
    /// <param name="O"></param>
    public void RemoveObserver(IObserver O);
    /// <summary>
    /// 主题改变时,通知观察者
    /// </summary>
    public void NotifyObserver();
}

具体主题代码:

public class WeatherData : ISubject
{
    private List<IObserver> observers;
    private float Temperature;
    private float Humidity;
    private float Pressure;

    /// <summary>
    /// WeatherData数据改变
    /// </summary>
    /// <param name="temperature"></param>
    /// <param name="humidity"></param>
    /// <param name="pressure"></param>
    public void setMeasurements(float temperature, float humidity, float pressure)
    {
        this.Temperature = temperature;
        this.Humidity = humidity;
        this.Pressure = pressure;
        measurementsChanged();
    }
    public void measurementsChanged()
    {
        NotifyObserver();
    }
    public WeatherData()
    {
        observers = new List<IObserver>();
    }
    /// <summary>
    /// 通知所有绑定的观察者
    /// </summary>
    public void NotifyObserver()
    {
        foreach (var item in observers)
        {
            item.Update(Temperature, Humidity, Pressure);
        }
    }
    /// <summary>
    /// 注册观察者
    /// </summary>
    /// <param name="O"></param>
    public void RegisterObserver(IObserver O)
    {
        observers.Add(O);
        if (observers.Contains(O))
        {
            Console.WriteLine("注册观察者成功");
        }
        else
        {
            Console.WriteLine("注册观察者失败");
        }
    }
    /// <summary>
    /// 移除观察者
    /// </summary>
    /// <param name="O"></param>
    public void RemoveObserver(IObserver O)
    {

        observers.Remove(O);
        if (observers.Contains(O))
        {
            Console.WriteLine("移除观察者失败");
        }
        else
        {
            Console.WriteLine("移除观察者成功");
        }
    }
    /// <summary>
    /// 获取温度
    /// </summary>
    /// <returns></returns>
    public float getTmperature()
    {
        return Temperature;
    }
    /// <summary>
    /// 获取湿度
    /// </summary>
    /// <returns></returns>
    public float getHumidity()
    {
        return Humidity;
    }
    /// <summary>
    /// 获取气压
    /// </summary>
    /// <returns></returns>
    public float getPressure()
    {
        return Pressure;
    }
}

布告板有共同的方法Display,所以提出来布告板相同的部分

/// <summary>
/// 布告板接口
/// </summary>
interface IDisplayElement
{
    public void Display();
}

观察者代码:

/// <summary>
/// 观测者接口
/// </summary>
public interface IObserver
{
    /// <summary>
    /// 改变接口
    /// </summary>
    /// <param name="temp"></param>
    /// <param name="humidity"></param>
    /// <param name="pressure"></param>
    public void Update(float temp,float humidity,float pressure);
}

具体观察者代码:

/// <summary>
/// 目前状况布告板
/// </summary>
public class CurrentConditionsDisplay : IObserver, IDisplayElement
{
    private float Temperature;
    private float Humidity;
    private float Pressure;
    private ISubject WeartherData;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="weatherData"></param>
    public CurrentConditionsDisplay(ISubject weatherData)
    {
        this.WeartherData = weatherData;
        weatherData.RegisterObserver(this);
    }
    /// <summary>
    /// 展示
    /// </summary>
    public void Display()
    {
        Console.WriteLine("Current conditions:{0}F 、{1}%humidity and {2}pressure", Temperature, Humidity, Pressure);
    }
    /// <summary>
    /// 改变
    /// </summary>
    /// <param name="temp"></param>
    /// <param name="humidity"></param>
    /// <param name="pressure"></param>
    public void Update(float temp, float humidity, float pressure)
    {
        this.Temperature = temp;
        this.Humidity = humidity;
        this.Pressure = pressure;
        Display();
    }
}

测试程序代码:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("推模式");
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay current = new CurrentConditionsDisplay(weatherData);

        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(85, 55, 33.4f);
        weatherData.setMeasurements(75, 43, 27.4f);
    }
}

运行结果:

图5 运行结果

2-3拉模式

图6 拉模式UML

主题如何送出通知
① 先调用setChanged()方法,标记状态已经改变的事实
② 然后调用两种NotifyObservers()方法中的一个:NotifyObservers()或NotifyObservers(object arg)

观察者如何接受通知
同以前一样,观察者实现更新的方法,但签名不太一样:Update(ISubject sub,object arg)

拉模式代码
拉模式与推模式有相同代码,所以只贴出来不同部分的代码。
具体主题代码:

class WeatherData : ISubject
{
    private List<IObserver> observers;
    private float Temperature;
    private float Humidity;
    private float Pressure;
    private bool changed = false;

    /// <summary>
    /// 构造函数
    /// </summary>
    public WeatherData()
    {
        observers = new List<IObserver>();
    }
    /// <summary>
    /// 主题状态改变,调用该方法
    /// </summary>
    /// <param name="temperature"></param>
    /// <param name="humidity"></param>
    /// <param name="pressure"></param>
    public void setMeasurements(float temperature, float humidity, float pressure)
    {
        this.Temperature = temperature;
        this.Humidity = humidity;
        this.Pressure = pressure;
        measurementsChanged();
    }
    public void measurementsChanged()
    {
        SetChanged();
        NotifyObserver();
    }

    /// <summary>
    /// 查看是否修改状态
    /// </summary>
    public void SetChanged()
    {
        changed = true;
    }
    /// <summary>
    /// 通知所有绑定的观察者
    /// </summary>
    /// <param name="arg">传入NotityObserver()的数据对象,如果没有说明则为空</param>
    public void NotifyObserver(object arg)
    {
        if (changed)
        {
            foreach (var item in observers)
            {
                item.Update(this, arg);
            }
            changed = false;
        }
    }
    /// <summary>
    /// 通知所有绑定的观察者
    /// </summary>
    public void NotifyObserver()
    {
        NotifyObserver(null);
    }
    /// <summary>
    /// 注册观察者
    /// </summary>
    /// <param name="O"></param>
    public void RegisterObserver(IObserver O)
    {
        observers.Add(O);
        if (observers.Contains(O))
        {
            Console.WriteLine("注册观察者成功");
        }
        else
        {
            Console.WriteLine("注册观察者失败");
        }
    }
    /// <summary>
    /// 移除观察者
    /// </summary>
    /// <param name="O"></param>
    public void RemoveObserver(IObserver O)
    {
        observers.Remove(O);
        if (observers.Contains(O))
        {
            Console.WriteLine("移除观察者失败");
        }
        else
        {
            Console.WriteLine("移除观察者成功");
        }
    }
    /// <summary>
    /// 获取温度
    /// </summary>
    /// <returns></returns>
    public float getTmperature()
    {
        return Temperature;
    }
    /// <summary>
    /// 获取湿度
    /// </summary>
    /// <returns></returns>
    public float getHumidity()
    {
        return Humidity;
    }
    /// <summary>
    /// 获取气压
    /// </summary>
    /// <returns></returns>
    public float getPressure()
    {
        return Pressure;
    }
}

观察者代码:

/// <summary>
/// 拉模式的观察者 接口
/// </summary>
public interface IObserver
{
    /// <summary>
    /// 改变接口
    /// </summary>
    public void Update(ISubject sub, Object arg);
}

具体观察者代码:

/// <summary>
/// 目前状况布告板
/// </summary>
class CurrentConditionsDisplay : IObserver, IDisplayElement
{
    ISubject Subject;
    private float Temperature;
    private float Humidity;

    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="sub">主题</param>
    public CurrentConditionsDisplay(ISubject sub)
    {
        this.Subject = sub;
        Subject.RegisterObserver(this);
    }
    public void Display()
    {
        Console.WriteLine("Current conditions:{0}F 、{1}%humidity ", Temperature, Humidity);
    }
    /// <summary>
    /// 修改部分
    /// </summary>
    /// <param name="sub">主题</param>
    /// <param name="arg"></param>
    public void Update(ISubject sub, object arg)
    {
        if (sub is WeatherData)
        {
            WeatherData weather = (WeatherData)sub;
            this.Temperature = weather.getTmperature();
            this.Humidity = weather.getHumidity();
        }
        Display();
    }
}

测试程序代码:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("推模式");
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay current = new CurrentConditionsDisplay(weatherData);

        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(85, 55, 33.4f);
        weatherData.setMeasurements(75, 43, 27.4f);
    }
}

运行结果:

图7 运行结果

三 总结

3-1 模式优缺点

观察者模式优点

观察者模式缺点

观察者模式推优点是实时、高效。缺点是:观察者会收到主题传送的内部状态;当观察者种类比较多,主题维护观察者比较麻烦;当观察者只需要一点数据是,会被迫收到一堆数据。

观察者模式拉优点:如果观察者众多,会将订阅关系放在Observer;观察者自行决定获取数据;当扩展功能时(增加更多的状态),只需改变自己的getting方法。缺点是:主题会暴露我们不想暴露的内部成员

3-2 适合场景

以下情况可以使用观察者模式:

3-3 模式应用

观察者模式使用非常广泛,凡是涉及一对一或者一对多的对象都可以使用观察者模式,例子:某购物网站执行发送后将打折信息发送给用户,某手机软件有活动点击发送后通知所有用户。

总结

上一篇 下一篇

猜你喜欢

热点阅读