Java设计模式:观察者模式

xiaoxiao2021-02-28  122

在生活实际中,经常会遇到多种对象关注一个对象数据变化的情况。例如,生活中有温度记录仪,当温度发生变化时,需要完成如下功能:记录温度日志,显示温度变化曲线,当温度越界时扬声器发出声音。可能写出以下程序段。

While(温度变化){

  记录温度日志;

  显示温度变化曲线;

  当温度越界时扬声器发出声音;

}

这种方法把所有功能集成字一起,但是当需求发生变化,例如新增新的温度监测功能或者要删除某种功能,程序都得修改,这就是我们不希望看到的结果。观察者设计模式则是解决这类问题的有效办法。

观察者模式设计两种角色:主题和观察者。在上面的例子中,温度无疑就是主题,而记录温度日志,显示温度变化曲线,当温度越界时扬声器发出声音 即是三个观察者。观察者需要时刻“关注”主题的变化而作出不同的工作,就好像程序员都要围绕着开发需求一样编写代码,需求一改,我们需要立马改代码!明白了这两种角色之后,下面来仔细看看这两者之间的关系需要有什么功能。

开发需求是经理定的,对于经理来说,他需要知道有哪几个程序员为它工作,并且它根据需求可以新增或者剔除为它工作的程序员。那由此可以得出下面几个重要结论。

1)主题要知道有哪些观察者对其进行监测,所以主题类里面需要有集合类成员集合。

2)既然包含观察者对象集合,那么观察者必须是多态的,这就要求有共同的接口。

3)主题应该有的功能:添加观察者,撤销观察者,并向观察者发送消息,特别是“推数据”(下文会讨论)的模式。这三个功能固定,主题类可以从固定接口派生。

根据以上编写观察者设计模式,需要完成以下功能类。

1.主题ISubject接口定义

2.主题类编写

3.观察者接口IObserve定义

4.观察者类实现

UML图如下

关键代码如下

1)观察者接口IObserver

public interface IObserver { //观察者接口 public void refresh(String data); }

2)主题接口ISubject

public interface ISubject{ public void register(IObserver obs); //注册观察者 public void unregister(IObserver obs); //撤销观察者 public void notifyObservers(); //通知所有观察者 }

3)主题实现类Subject

public class Subject implements ISubject { private Vector<IObserver> vec = new Vector<IObserver>(); private String data; public String getData(){ return data; } public void setData(String data){ this.data = data; } public void register(IObserver obs){ //主题添加观察者 vec.add(obs); } public void unregister(IObserver obs){ //主题撤销观察者 vec.remove(obs); } public void notifyObservers(){ //主题通知所有观察者进行数据响应 for(int i=0;i<vec.size();i++){ IObserver obs = vec.get(i); obs.refresh(data); } } }

4)具体观察者Observer

public class Observer implements IObserver { public void refresh(String data){ System.out.println("I have received the data:" + data); } }

5)测试类Test

  View Code

 

有了基本的了解之后,下面再深入地剖析一下观察者模式ba

1.推数据与拉数据

推数据,简单理解就是当主题的数据变动时主动发送数据给观察者,提醒观察者数据有所变动。而拉数据,顾名思义也就是观察者主动索取主题的数据,并不由主题主动发送。那上面我们的代码样例,你说是推数据还是拉数据?当然是推数据(这应该不难看出来)。

在拉数据模式中,观察者子类对象必须能获取主题Subject对象,代码示例如下。

IObserver

public interface IObserver{ //观察者接口 public void refresh(ISubject obj); //采用“拉”数据方式 }

ISubject 同上这里就不再重复列出

Subject

public class Subject implements ISubject { private Vector<IObserver> vec = new Vector<IObserver>(); private String data; public String getData(){ return data; } public void setData(String data){ this.data = data; } public void register(IObserver obs){ //主题添加观察者 vec.add(obs); } public void unregister(IObserver obs){ //主题撤销观察者 vec.remove(obs); } public void notifyObservers(){ //主题通知所有观察者进行数据响应 for(int i=0;i<vec.size();i++){ IObserver obs = vec.get(i); obs.refresh(this); //这里有所不同 } } }

Observer

public class Observer implements IObserver { public void refresh(ISubject obj){ Subject subject = (Subject)obj; System.out.println("I have received the data:" + subject.getData(); } }

UML如下

 

2.增加抽象类层AbstractSubject

在前面我们已经分析了主题应该有的功能,而大部分主题都有类似的功能,因为是比较通用的方法。那么每个主题类的代码就显得重复了,所以用一个中间层来解决代码重复问题是一个比较好的方法。

  View Code

3.泛型的设计

上面的代码中,有一个欠缺的问题就是,主题的数据data并不一定是String类型,于是我们想到应该把接口代码改为泛型。不仅仅主题ISubject需要更改,当然IObserver也要改为泛型接口。这里就不演示代码了。

4.JDK中的观察者设计模式

JDK的java.util包提供了系统的主题类Observable类以及观察者Observer,其(部分)UML类图如下

很明显,Observer类相当于上面的IObserver观察者接口类,其中的update方法中第一个参数是Observable类型,表明采用“拉”数据方式;Observable相当于上面的主题类Subject。需要主要的是hasChange()方法主要是设置或获得changed成员变量的值或者状态,changed为true时表明主题中心的数据发生了变化。

下面我们利用JDK中的Observer,Observable完成观察者模式

Subject类

public class Subject extends java.util.Observable { String data; public String getData(){ return data; } public void setData(String data){ this.data = data; //更新数据 setChanged(); //置更新数据标志 notifyObservers(null); //通知各个具体观察者 } }

OneObserver

public class OneObserver implements java.util.Observer { public void update(Observable arg0,Object arg1){ Subject subject = (Subject)arg0; System.out.println("The data is :" + subject.getData()); } }

简单测试

  View Code

 

最后,当然是给一个应用场景(机房温度监测仿真功能)

监测一个机房的温度数据。要求 1.定间距采集温度数值 2.记录采集温度数值 3.标识异常温度数据 4.当温度连续超过比较值n次,发送报警信息

分析:监测功能是以温度为中心的,因此观察者模式实现程序架构比较方便。

总体思想如下:温度作为主体类,两个观察者,一个负责记录数据,另一个观察者负责处理异常。将时间采样间距数据,温度异常值等记录在xml配置文件中,报警信息的发送邮件处理。

mysql的数据表设计 normal表记录所有温度记录,abnormal表记录异常温度记录。这样的好处是abnormal表的记录远比normal表的记录少得多,将来查询异常记录信息会非常快。数据的产生器采用反射技术。具体代码如下

mysql表的简单设计

  View Code

mysql封装处理类

  View Code

info.xml

  View Code

条件类factor

  View Code

主题类Subject

  View Code

数据记录观察者类DataObserver

  View Code

异常数据观察者类AbnormalObserver

  View Code

仿真数据生成器类均从ISimuData自定义接口派生。

ISimuData接口

  View Code

DataRandom 继承ISimuData接口,生成数据的方法多种多样,这里读取record.txt文件的数据

  View Code

Test测试类

  View Code

 

转载请注明原文地址: https://www.6miu.com/read-25049.html

最新回复(0)