java observer泛型_Java设计模式:观察者模式

本文通过一个温度监测的例子介绍了观察者模式,分析了观察者和被观察者(主题)的角色,以及如何使用Java实现观察者模式。讨论了推数据和拉数据的区别,并展示了使用抽象类和泛型优化设计的方案。最后,给出了一个基于观察者模式的机房温度监测应用实例,涉及数据记录、异常处理和邮件通知功能。
摘要由CSDN通过智能技术生成

问题提出:

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

While(温度变化){

记录温度日志;

显示温度变化曲线;

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

}

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

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

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

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

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

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

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

1.主题ISubject接口定义

2.主题类编写

3.观察者接口IObserve定义

4.观察者类实现

UML图如下

c89bd745f7a2d875a789a823ad704560.png

关键代码如下

1)观察者接口IObserver

public interfaceIObserver {//观察者接口

public voidrefresh(String data);

}

2)主题接口ISubject

public interfaceISubject{public void register(IObserver obs); //注册观察者

public void unregister(IObserver obs); //撤销观察者

public void notifyObservers(); //通知所有观察者

}

3)主题实现类Subject

public class Subject implementsISubject {private Vector vec = new Vector();privateString data;publicString getData(){returndata;

}public voidsetData(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

IObserver obs=vec.get(i);

obs.refresh(data);

}

}

}

4)具体观察者Observer

public class Observer implements IObserver{public voidrefresh(String data){

System.out.println("I have received the data:" +data);

}

}

5)测试类Test

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public classTest {public static voidmain(String[] args) {

IObserver obs= newObserver();

Subject subject= newSubject();

subject.register(obs);

subject.setData("Hello World!");

subject.notifyObservers();

}

}

View Code

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

1.推数据与拉数据

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

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

IObserver

public interfaceIObserver{//观察者接口

public void refresh(ISubject obj); //采用“拉”数据方式

}

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

Subject

public class Subject implementsISubject {private Vector vec = new Vector();privateString data;publicString getData(){returndata;

}public voidsetData(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

IObserver obs=vec.get(i);

obs.refresh(this); //这里有所不同

}

}

}

Observer

public class Observer implementsIObserver {public voidrefresh(ISubject obj){

Subject subject=(Subject)obj;

System.out.println("I have received the data:" +subject.getData();

}

}

UML如下

c9d0bfec0606de8641e45115d79bde83.png

2.增加抽象类层AbstractSubject

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

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public abstract class AbstractSubject implementsISubject{

Vector vec = new Vector();public voidregister(IObserver obs){if(!vec.contains(obs)){

vec.add(obs);

}

}public voidunregister(IObserver obs){if(vec.contains(obs)){

vec.remove(obs);

}

}public voidnotifyObservers(){for(int i=0;i

IObserver obs=vec.get(i);

obs.refresh(this);

}

}

}

View Code

3.泛型的设计

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

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

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

02f0af7ff35c3afd2e3e4cc94ead164a.png

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

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

Subject类

public class Subject extendsjava.util.Observable {

String data;publicString getData(){returndata;

}public voidsetData(String data){this.data = data; //更新数据

setChanged(); //置更新数据标志

notifyObservers(null); //通知各个具体观察者

}

}

OneObserver

public class OneObserver implementsjava.util.Observer {public voidupdate(Observable arg0,Object arg1){

Subject subject=(Subject)arg0;

System.out.println("The data is :" +subject.getData());

}

}

简单测试

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public classTest {public static void main(String[] args) throwsException{

java.util.Observer obj= newOneObserver();

Subject s= newSubject();

s.addObserver(obj);

s.setData("Hello World!");

}

}

View Code

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

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

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

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

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

mysql表的简单设计

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

create tablenormal(

wenduvalueint,

recordtime Date

)create tableabnormal(

abnormalvalueint,

recordtime Date

)

View Code

mysql封装处理类

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

import java.sql.*;importjava.util.List;importjava.util.Vector;/*** Created by lenovo on 2017/4/18.*/

public classDbProc {private String strDriver = "com.mysql.jdbc.Driver";private String strDb = "jdbc:mysql://localhost:3306/buildModel";private String strUser = "root";private String strPwd = ""; //注意测试时候strPwd要加上自己本地mysql的账户密码

privateConnection conn;public Connection connect() throwsException{

Class.forName(strDriver);

conn=DriverManager.getConnection(strDb,strUser,strPwd);returnconn;

}public int executeUpdate(String strSQL) throwsException{

Statement stm=conn.createStatement();int n =stm.executeUpdate(strSQL);

stm.close();returnn;

}public List executeQuery(String strSQL) throwsException{

List l= newVector();

Statement stm=conn.createStatement();

ResultSet rst=stm.executeQuery(strSQL);

ResultSetMetaData rsmd=rst.getMetaData();while(rst.next()){

Vector unit= newVector();for(int i=1;i<=rsmd.getColumnCount();i++){

unit.add(rst.getString(i));

}

l.add(unit);

}returnl;

}public void close() throwsException{

conn.close();

}

}

View Code

info.xml

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

Observer

2

30

5

DataRandom

View Code

条件类factor

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packageObserver.Example;/*** Created by lenovo on 2017/4/19.*/

//用于主题向观察者传送条件对象

public classFactor {private int limit; //温度预警值

private int times; //连续越过预警值次数极限值

private String address; //邮件地址

public Factor(int limit, inttimes, String address) {this.limit =limit;this.times =times;this.address =address;

}public intgetLimit() {returnlimit;

}public intgetTimes() {returntimes;

}publicString getAddress() {returnaddress;

}

}

View Code

主题类Subject

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packageObserver.Example;/*** Created by lenovo on 2017/4/19.*/

public class Subject extendsjava.util.Observable {private intdata;privateFactor factor;public void setFactor(Factor factor){ //设置条件对象

this.factor =factor;

}public intgetData(){returndata;

}public void setData(intdata){this.data =data;

setChanged();//observable类中的方法

notifyObservers(factor); //将条件对象广播给各观察者

}

}

View Code

数据记录观察者类DataObserver

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packageObserver.Example;importBuildModel.Example.DbProc;importjava.util.Observable;importjava.util.Observer;/*** Created by lenovo on 2017/4/19.*/

public class DataObserver implementsObserver {//将采集到的所有数据保存到normal表中

public voidupdate(Observable obj,Object factor){

Subject subject=(Subject)obj;

String strSQL= "insert into normal values(" + subject.getData() + ",now())";

DbProc dbobj= newDbProc();try{

dbobj.connect();

dbobj.executeUpdate(strSQL);

dbobj.close();

}catch(Exception ex){

ex.printStackTrace();

}

}

}

View Code

异常数据观察者类AbnormalObserver

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packageObserver.Example;importBuildModel.Example.DbProc;importjavax.mail.Message;importjavax.mail.Session;importjavax.mail.Transport;importjavax.mail.internet.InternetAddress;importjavax.mail.internet.MimeMessage;importjava.util.Observable;importjava.util.Observer;importjava.util.Properties;/*** Created by lenovo on 2017/4/19.*/

public class AbnormalObserver implementsObserver {private int c = 0; //温度异常值累积

public voidupdate(Observable obj,Object factor){

Subject subject=(Subject)obj;

Factor fac=(Factor)factor;if(subject.getData()

c = 0;return;

}

c++;

saveToAbnormal(subject);//将越界数据保存到异常数据表

if(c == fac.getTimes()){ //如越界累积次数=条件极限次数

sendEmail(fac); //则发送邮件

c = 0; //重新开始累积

}

}private voidsaveToAbnormal(Subject subject){

String strSQL= "insert into abnormal values(" + subject.getData() + ",now())";

DbProc dbProc= newDbProc();try{

dbProc.connect();

dbProc.executeUpdate(strSQL);

dbProc.close();

}catch(Exception ex){

ex.printStackTrace();

}

}private voidsendEmail(Factor factor){

String host= "smtp.163.com";

String from= ""; //发件人地址

String to =factor.getAddress();

String userName= "";

String pwd= "";

Properties props= newProperties();

props.put("mail.smtp.host",host);

props.put("mail.smtp.auth","true");

Session session=Session.getDefaultInstance(props);

session.setDebug(true);

MimeMessage msg= newMimeMessage(session);try{

msg.setFrom(newInternetAddress(from));

msg.addRecipient(Message.RecipientType.TO ,newInternetAddress(to));

msg.setSubject("温度预警信息");

msg.setText("机房处于异常状态");

msg.saveChanges();

Transport transport= session.getTransport("smtp");

transport.connect(host,userName,pwd);

transport.sendMessage(msg,msg.getRecipients(Message.RecipientType.TO));

}catch(Exception ex){

ex.printStackTrace();

}

}

}

View Code

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

ISimuData接口

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public interface ISimuData{voidopen();voidclose();booleanhasNext();

T next();

}

View Code

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

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

public class DataRandom implements ISimuData{

Scanner input= null;public voidopen(){

File file= new File("record.txt");try{

input= newScanner(file);

}catch(Exception ex){

ex.printStackTrace();

}

}public voidclose() {

input.close();

}public booleanhasNext() {if(input.hasNext()){return true;

}return false;

}publicInteger next() {returnInteger.parseInt(input.next());

}

}

View Code

Test测试类

8f900a89c6347c561fdf2122f13be562.png

961ddebeb323a10fe0623af514929fc1.png

packageObserver.Example;importjava.io.FileInputStream;importjava.util.Observer;importjava.util.Properties;/*** Created by lenovo on 2017/4/19.*/

public classTest {public static void main(String[] args) throwsException{//File directory = new File("");//设定为当前文件夹//System.out.println(directory.getCanonicalPath());

FileInputStream in = new FileInputStream("info.xml");

Properties p= newProperties();

p.loadFromXML(in);int range = Integer.parseInt(p.getProperty("range"));

String reflectClassName= p.getProperty("reflect");int limit = Integer.parseInt(p.getProperty("limit"));int nums = Integer.parseInt(p.getProperty("nums"));

String address= p.getProperty("address");

Factor factor= newFactor(limit,nums,address);

in.close();

Subject s= newSubject();

Observer obj= newDataObserver();

Observer obj2= newAbnormalObserver();

s.addObserver(obj);

s.addObserver(obj2);

s.setFactor(factor);//主题设置条件,以备广播给观察者对象用//利用反射技术数据仿真

ISimuData sdobj =(ISimuData)Class.forName(reflectClassName).newInstance();

sdobj.open();while(sdobj.hasNext()){int value =sdobj.next();

s.setData(value);try{

Thread.sleep(range*1000);

}catch(Exception e){

e.printStackTrace();

}

}

sdobj.close();

}

}

View Code

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值