Java设计模式:观察者模式
问题提出:
在生活实际中,经常会遇到多种对象关注一个对象数据变化的情况。例如,生活中有温度记录仪,当温度发生变化时,需要完成如下功能:记录温度日志,显示温度变化曲线,当温度越界时扬声器发出声音。可能写出以下程序段。
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
public class Test { public static void main(String[] args) { IObserver obs = new Observer(); Subject subject = new Subject(); subject.register(obs); subject.setData("Hello World!"); subject.notifyObservers(); } }
有了基本的了解之后,下面再深入地剖析一下观察者模式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
在前面我们已经分析了主题应该有的功能,而大部分主题都有类似的功能,因为是比较通用的方法。那么每个主题类的代码就显得重复了,所以用一个中间层来解决代码重复问题是一个比较好的方法。
public abstract class AbstractSubject implements ISubject{ Vector<IObserver> vec = new Vector<IObserver>(); public void register(IObserver obs){ if(!vec.contains(obs)){ vec.add(obs); } } public void unregister(IObserver obs){ if(vec.contains(obs)){ vec.remove(obs); } } public void notifyObservers(){ for(int i=0;i<vec.size();i++){ IObserver obs = vec.get(i); obs.refresh(this); } } }