1.简介 观察者模式是一种对象行为模式。 在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。
它定义对象间的一种一对多的依赖关系: 当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。 主体是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅并接收通知。
观察者模式(Observer)完美的将观察者和被观察的对象分离开。 观察者模式被广泛应用于软件界面元素之间的交互,在业务对象之间的交互、权限管理等方面。
此种模式通常被用来实现事件处理系统
别名:
发布-订阅(Publish/Subscribe)模式
模型-视图(Model/View)模式
源-监听器(Source/Listener)模式
从属者(Dependents)模式
2. 设计原则
交互对象之间尽量采用低耦合设计
封装代码中经常变化的数据
3. 模式结构
观察者模式所涉及的角色有:
抽象主题(Subject)角色: 抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。 抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。
具体主题(ConcreteSubject)角色: 将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。 具体主题角色又叫做具体被观察者(Concrete Observable)角色。
抽象观察者(Observer)角色: 为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。
具体观察者(ConcreteObserver)角色: 存储与主题的状态自恰的状态。 具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。 如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。
4. 推模型与拉模型 在观察者模式中,又分为推模型和拉模型两种方式。
4.1 推模型 主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。
4.2 拉模型 主题对象在通知观察者的时候,只传递少量信息。 如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。
一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
4.3 二者比较
5. 模式案例 5.1 案例描述 你将设计一个气象监测应用,它要求具备以下几个功能:
从上图可以分析出各个类在观察者模式中他们的角色:
抽象主题(Subject)角色:Subject
具体主题(ConcreteSubject)角色:WeatherData
抽象观察者(Observer)角色:Observer
具体观察者(ConcreteObserver)角色:StatisticsDisplay;ThirdPartDisplay;ForecastDisplay;
5.3 代码编写 5.3.1 抽象主题编写 1 2 3 4 5 6 public interface Subject { public void registerObserver (Observer a ) ; public void removeObserver (Observer a ) ; public void notifyObserver ( ) ; }
5.3.2 抽象观察者编写 1 2 3 4 5 public interface Observer { public void update (Data x ) ; public void pulldate ( ) ; }
5.3.3 具体主题编写 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 38 39 40 41 42 public class WeatherData :Subject { public struct Data { public double temperature; public double humidity; public double pressure; } private Data data; List<Observer> listeners; public Data Datas { get => data;} public WeatherData ( ) { listeners = new List<Observer>(); measurementsChanged(); data = new Data(); } public void registerObserver (Observer a ) => listeners.Add(a); public void removeObserver (Observer a ) => listeners.Remove(a); public void notifyObserver ( ) { foreach (Observer i in listeners) { i.update(data); } } public void measurementsChanged ( ) { getRandomDate(); notifyObserver(); } private void getRandomDate ( ) { Random random = new Random(); data.humidity = random.NextDouble(); data.temperature=random.Next(-40 ,40 )+random.NextDouble(); data.pressure = random.Next(1 , 2 )+random.NextDouble(); } }
5.3.4 具体观察者编写 显示模式的接口:
1 2 3 4 public interface DisplayElement { public void display ( ) ; }
三种显示模式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public class CurrentConditionsDisplay :Observer ,DisplayElement { private Subject weatherData; private Data d; public CurrentConditionsDisplay (Subject x ) { weatherData = x; weatherData.registerObserver(this ); } public void update (Data x ) { d = x; display(); } public void pulldate ( ) => update(((WeatherData)weatherData).Datas); public void display ( ) { Console.WriteLine("---当前天气---" ); Console.WriteLine("当前温度:" + d.temperature.ToString("F2" ) + "\t当前气压:" + d.pressure.ToString("F2" ) + "\t当前湿度:" +d.humidity.ToString("F2" ) + "\n" ) ; } }
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 38 39 40 41 public class StatisticsDisplay : Observer , DisplayElement { Subject weatherData; private Queue<Data> dates; public StatisticsDisplay (Subject x ) { weatherData = x; weatherData.registerObserver(this ); dates = new Queue<Data>(); } public void update (Data x ) { if (dates.Count== 3 )dates.Dequeue(); dates.Enqueue(x); display(); } public void pulldate ( ) => update(((WeatherData)weatherData).Datas); public void display ( ) { Double[] average = { 0 , 0 , 0 }; foreach (Data i in dates) { average[0 ] += i.temperature; average[1 ] += i.pressure; average[2 ] += i.humidity; } Console.WriteLine("***天气统计***" ); Console.WriteLine("最高温度:" + dates.Max(x => x.temperature).ToString("F2" ) + "\t最低温度:" + dates.Min(x => x.temperature).ToString("F2" ) + "\t平均温度:" + (average[0 ] / dates.Count).ToString("F2" )); Console.WriteLine("最高气压:" + dates.Max(x => x.pressure).ToString("F2" ) + "\t最低气压:" + dates.Min(x => x.pressure).ToString("F2" ) + "\t平均气压:" + (average[1 ] / dates.Count).ToString("F2" )); Console.WriteLine("最高湿度:" + dates.Max(x => x.humidity).ToString("F2" ) + "\t最低湿度:" + dates.Min(x => x.humidity).ToString("F2" ) + "\t平均湿度:" + (average[2 ] / dates.Count).ToString("F2" ) + "\n" ); } }
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 public class ForecastDisplay : Observer , DisplayElement { private Subject weatherData; Data d; String weather; double t; public ForecastDisplay (Subject x ) { weatherData = x; weatherData.registerObserver(this ); } public void update (Data data ) { d = data; if (d.humidity > 0.5 && d.pressure > 1 ) { if (d.temperature<1 ) weather = "雪" ; else weather = "雨" ; } else weather = "晴" ; t = d.temperature; display(); } public void pulldate ( ) => update(((WeatherData)weatherData).Datas); public void display ( ) { Console.WriteLine("===天气预报===" ); Console.WriteLine("今日天气:" + weather + "\t 温度:" + t.ToString("F2" )+"\n" ); ; } }
5.3.5 测试 1 2 3 4 5 6 7 8 9 10 11 static void Main (string [] args ) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay s1 = new CurrentConditionsDisplay(weatherData); StatisticsDisplay s2 = new StatisticsDisplay(weatherData); ForecastDisplay s3 = new ForecastDisplay(weatherData); for (int i=0 ;i<3 ;i++) { weatherData.measurementsChanged(); } }
结果如下图所示:
6. java中的观察者模式类
略 https://www.jianshu.com/p/fc4554cda68d
8. C#中实现观察者模式的方法 整理自:https://www.jb51.net/article/63077.htm
8.1 利用事件
请先学习C#事件
我们将上面讲的天气数据监测应用改成用C#事件实现的观察者模式
在WeatherData类中声明委托与事件
1 2 public delegate void WeatherdateUpdate (Data data ) ;public static event WeatherdateUpdate updata;
在WeatherData类中编写事件触发函数
1 2 3 4 5 6 7 public void notifyByEvent ( ) { if (updata!=null ) { updata(data); } }
在三个具体观察者中编写事件处理函数并在构造函数中订阅到事件
1 2 3 4 5 6 7 8 WeatherData.updata += update;public void update (Data x ) { d = x; display(); }
触发事件
1 2 3 4 5 6 public void measurementsChanged ( ) { getRandomDate(); notifyByEvent(); }
运行输出结果与用接口实现的效果完全相同。
8.2 利用IObservable和IObserver实现 与java中类似,略
8.3 利用Action函数式 略
7. 优缺点 7.1 优点
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
目标与观察者之间建立了一套触发机制。
7.2 缺点