【设计模式】观察者模式
1、概述
背景
天气监测站会有每天的气温、湿度、气压,并且当有数据更新时,会把它们推送给百度、新浪等网站。
定义
又被称为发布-订阅(Publish/Subscribe)模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。
结构
在观察者模式中有如下角色:
- Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
- ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
- Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
- ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
2、实现思路
天气监测站会有每天的气温、湿度、气压,并且当有数据更新时,会把它们推送给百度、新浪等网站。
我们首先看看不使用观察者模式的代码:
public class WeatherStation
{
private BaiduWeather _baiduWeather;
public WeatherStation(BaiduWeather baiduWeather)
{
this._baiduWeather = baiduWeather;
}
/// <summary>
/// 气温
/// </summary>
private double Temperature { get; set; }
/// <summary>
/// 湿度
/// </summary>
private double Humidity { get; set; }
/// <summary>
/// 气压
/// </summary>
private double Pressure { get; set; }
public double GetTemperature()
{
return this.Temperature;
}
public double GetHumidity()
{
return this.Humidity;
}
public double GetPressure()
{
return this.Pressure;
}
public void WeatherChange()
{
_baiduWeather.Update(this.Temperature,this.Humidity,this.Pressure);
}
public void SetWeather(double temperature, double humidity, double pressure)
{
this.Temperature = temperature;
this.Humidity = humidity;
this.Pressure = pressure;
WeatherChange();
}
}
public class BaiduWeather
{
/// <summary>
/// 气温
/// </summary>
private double Temperature { get; set; }
/// <summary>
/// 湿度
/// </summary>
private double Humidity { get; set; }
/// <summary>
/// 气压
/// </summary>
private double Pressure { get; set; }
public void Update(double temperature, double humidity, double pressure)
{
this.Temperature = temperature;
this.Humidity = humidity;
this.Pressure = pressure;
Display();
}
public void Display()
{
Console.WriteLine($"气温:{this.Temperature}℃,气压:{this.Pressure}hPa,湿度:{this.Humidity}%");
}
}
class Program
{
static void Main(string[] args)
{
BaiduWeather baiduWeather = new BaiduWeather();
WeatherStation weatherStation = new WeatherStation(baiduWeather);
weatherStation.SetWeather(36.2,71,1004.5);
}
}
=======================================
气温:36.2℃,气压:1004.5hPa,湿度:71%
这种写法:
- 违法了开闭原则:如果又需要推送到新浪天气,那么就得修改WeatherStation这个类。
- 无法动态添加第三方(新浪天气)。
下面我们使用策略模式实现:
public interface ISubject
{
public abstract void AddObservers(Observer observer);
public abstract void DeleteObservers(Observer observer);
public abstract void NotifyObservers();
}
/// <summary>
/// 天气站
/// </summary>
public class WeatherStation: ISubject
{
private List<Observer> _observers;
public WeatherStation()
{
this._observers = new List<Observer>();
}
/// <summary>
/// 气温
/// </summary>
private double Temperature { get; set; }
/// <summary>
/// 湿度
/// </summary>
private double Humidity { get; set; }
/// <summary>
/// 气压
/// </summary>
private double Pressure { get; set; }
public double GetTemperature()
{
return this.Temperature;
}
public double GetHumidity()
{
return this.Humidity;
}
public double GetPressure()
{
return this.Pressure;
}
public void SetWeather(double temperature, double humidity, double pressure)
{
this.Temperature = temperature;
this.Humidity = humidity;
this.Pressure = pressure;
NotifyObservers();
}
public void AddObservers(Observer observer)
{
_observers.Add(observer);
}
public void DeleteObservers(Observer observer)
{
if (_observers.Contains(observer))
_observers.Remove(observer);
}
public void NotifyObservers()
{
foreach (var observer in _observers)
{
observer.Update(this.Temperature, this.Humidity, this.Pressure);
}
}
}
/// <summary>
/// 观察者
/// </summary>
public class Observer
{
/// <summary>
/// 气温
/// </summary>
private double Temperature { get; set; }
/// <summary>
/// 湿度
/// </summary>
private double Humidity { get; set; }
/// <summary>
/// 气压
/// </summary>
private double Pressure { get; set; }
public void Update(double temperature, double humidity, double pressure)
{
this.Temperature = temperature;
this.Humidity = humidity;
this.Pressure = pressure;
Display();
}
public void Display()
{
Console.WriteLine($"{this.GetType().Name}气温:{this.Temperature}℃,气压:{this.Pressure}hPa,湿度:{this.Humidity}%");
}
}
public class BaiduWeather:Observer
{
}
public class SinaWeather:Observer
{
}
class Program
{
static void Main(string[] args)
{
WeatherStation weatherStation = new WeatherStation();
Observer baiduWeather = new BaiduWeather();
Observer sinaWeather = new SinaWeather();
weatherStation.AddObservers(baiduWeather);
weatherStation.AddObservers(sinaWeather);
weatherStation.SetWeather(36.2, 71, 1004.5);
Console.WriteLine("===============================");
weatherStation.DeleteObservers(baiduWeather);
weatherStation.SetWeather(37, 72, 1004.4);
}
}
===============================
BaiduWeather气温:36.2℃,气压:1004.5hPa,湿度:71%
SinaWeather气温:36.2℃,气压:1004.5hPa,湿度:71%
===============================
SinaWeather气温:37℃,气压:1004.4hPa,湿度:72%
3、优缺点
优点
- 降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。
- 被观察者发送通知,所有注册的观察者都会收到信息【可以实现广播机制】。
缺点
- 如果观察者非常多的话,那么所有的观察者收到被观察者发送的通知会耗时。
- 如果被观察者有循环依赖的话,那么被观察者发送通知会使观察者循环调用,会导致系统崩溃。
4、使用场景
- 对象间存在一对多关系,一个对象的状态发生改变会影响其他对象。
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面时。