首页 > 代码库 > 观察者模式小试
观察者模式小试
观察者模式又叫订阅-发布模式,也是非常常用的设计模式之一。
一、介绍
还是先来看一下《研磨设计模式》的介绍——定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变的时候,所有依赖于它的对象都得到通知,并被自动更新。
观察者模式的本质:触发联动。
什么意思呢?说白了,就是说一个对象的状态发生改变,另一个对象自动做出响应。怎样能够使一个目标对象的状态发生改变时,观察者对象自动做出响应呢?
很简单,让目标对象持有观察者对象就可以了。如果一个目标对象有多个观察者,每次目标对象的状态改变,就自动遍历自己持有的观察者对象,将自己状态改变的情况通知观察者,就是传递参数给观察者。观察者模式无非就是这样。
参看我的博文中介者模式小试,可知道,观察者模式和中介者模式有很多相似的地方。组件间传递信息时,也经常将自身传过去,然后处理时使用强制类型转换进行处理。不过中介者一般是多个组件类将自身传递给代理类,让代理类统一处理组件间的交互。而观察者模式则是反过来,目标类发生改变时,一般将自身传递给自己持有的每个观察者,这样就激活了观察者的方法。
二、我的实现
Swing中包含了大量的观察者模式的实现。为了便于理解,在这里我也模仿Swing。我们都知道在画板上画画,每次我们在画板上点击鼠标左键,马上,画板上上就出现了相应的点。拖住不放,就可以画出一条线。这是为什么呢?我们假设画板作为目标对象,有一个监听器在监听。每次点击左键都会产生一个事件,画板会马上接受这样一个事件,然后处理之后传给监听器,监听器把它画出来。
我要模拟的就是这个过程。如下:
1、一个抽象的目标类:
//抽象的目标类 public abstract class Subject { //监听器列表 protected List<Listener> listenerList = new ArrayList<Listener>(); //添加监听器 public void addListener(Listener listener) { listenerList.add(listener); } //移除监听器 public void removeListener(Listener listener) { listenerList.remove(listener); } //通知所有监听器 abstract void notifyListener(); }
2、监听器只是一个标识接口,不实现任何方法:
public interface Listener { }
3、将触发事件的因素封装起来,成为一个Event类,鼠标事件如下:
//模拟鼠标事件 public class MouseEvent { //模拟鼠标左、中、右键 public static final int BUTTON1 = 1; public static final int BUTTON2 = 2; public static final int BUTTON3 = 3; private int x; private int y; private int ClickCount; public int getClickCount() { return ClickCount; } public void setClickCount(int clickCount) { ClickCount = clickCount; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } }
4、每次都需要从鼠标事件中取出鼠标位置,封装成屏幕上的点,PointOnScreen类,如下:
public class PointOnScreen { private int x; private int y; public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } }
5、系统监听器,实现了标识接口Listener:
//系统监听器 public class SystemListener implements Listener { // 在屏幕上将这个图形画出来 public void drawOnScreen(List<PointOnScreen> graph) { System.out.println(); System.out.println("刷新时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date())); for (PointOnScreen point : graph) { System.out.println("当前画到的点是——————(" + point.getX() + "," + point.getY() + ")"); } } }
6、下面是最重要的目标具体类——画板类:
public class Panel extends Subject { // 表示图形 private List<PointOnScreen> graph = new ArrayList<PointOnScreen>(); // 添加鼠标事件 public void addKeyEvent(MouseEvent event) { // 处理鼠标事件 PointOnScreen point = new PointOnScreen(); point.setX(event.getX()); point.setY(event.getY()); graph.add(point); // 通知所有监听器 notifyListener(); } @Override void notifyListener() { // 遍历每一个PanelListener,画图! for (Listener listener : listenerList) { if (listener instanceof SystemListener) { ((SystemListener) listener).drawOnScreen(graph); } } } }
7、至此,已经完成,我们来测试一下:
public class Test { public static void main(String[] args) { //创建MouseEvent,几个鼠标事件 MouseEvent event1 = new MouseEvent(); event1.setX(1); event1.setY(2); MouseEvent event2 = new MouseEvent(); event2.setX(2); event2.setY(2); MouseEvent event3 = new MouseEvent(); event3.setX(2); event3.setY(3); //创建目标类 Panel panel = new Panel(); //系统监听器 SystemListener autoListener = new SystemListener(); //注册监听器 panel.addListener(autoListener); //添加事件,模拟鼠标点击操作 panel.addKeyEvent(event1); panel.addKeyEvent(event2); panel.addKeyEvent(event3); } }
8、结果如下:
刷新时间:2014-04-29 15:44:26.546 当前画到的点是——————(1,2) 刷新时间:2014-04-29 15:44:26.548 当前画到的点是——————(1,2) 当前画到的点是——————(2,2) 刷新时间:2014-04-29 15:44:26.548 当前画到的点是——————(1,2) 当前画到的点是——————(2,2) 当前画到的点是——————(2,3)
如上,已经模拟出了画图的过程。
三、推模型和拉模型
什么是推模型和拉模型呢?上面例子中,事件源是什么呢?是MouseEvent。可是传给监听器对象的时候,我们是将MouseEvent包装成PointOnScreen对象去传递的。这就是推模型。
相对的,拉模型指的就是,传递信息给监听器的时候,将本身的引用传递过去,那么监听器对象希望处理什么信息就处理什么信息,那就是拉模型。
我们将Panel改变一下:
public class Panel extends Subject { // 表示图形 private List<PointOnScreen> graph = new ArrayList<PointOnScreen>(); private KeyEvent keyEvent; public KeyEvent getKeyEvent(){ return keyEvent; } // 添加鼠标事件 public void addKeyEvent(MouseEvent event) { // 处理鼠标事件 PointOnScreen point = new PointOnScreen(); point.setX(event.getX()); point.setY(event.getY()); graph.add(point); // 通知所有监听器 notifyListener(); } void notifyListener(){ for (Listener listener : listenerList) { if (listener instanceof SystemListener) { //将自身对象传过去 listener.update(this); } } } }
然后,把SystemListener变成这样:
//系统监听器 public class SystemListener implements Listener { // 在屏幕上将这个图形画出来 public void drawOnScreen(List<PointOnScreen> graph) { System.out.println(); System.out.println("刷新时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date())); for (PointOnScreen point : graph) { System.out.println("当前画到的点是——————(" + point.getX() + "," + point.getY() + ")"); } } // 表示图形 private List<PointOnScreen> graph = new ArrayList<PointOnScreen>(); public void update(Subject subject) { if (subject instanceof Panel) { MouseEvent event = ((Panel) subject).getMouseEvent(); // 处理鼠标事件 PointOnScreen point = new PointOnScreen(); point.setX(event.getX()); point.setY(event.getY()); graph.add(point); drawOnScreen(graph); } } }
如上,目标对象传递信息的时候将自身传递过去,监听器处理信息的时候,需要什么就从目标对象哪里拿什么,非常方便。这就是拉模型。
四、Java中的观察者模式
要实现观察者模式,其实完全不用那么麻烦,目标类和监听者接口Java已经帮我们实现了。目标类是java.util.Observable,监听器接口是java.util.Observer
即,目标类要继承java.util.Observable,监听器接口实现java.util.Observer。怎么传值呢?
目标类状态改变之后,调用这样几个方法:
//状态改变 this.keyEvent = event; //状态改变了,不可少 this.setChanged(); // 拉模型 this.notifyObservers(); //推模型所传的对象 this.notifyObservers(graph);
同时,监听器接口实现了这样一个方法:
public void update(Observable o, Object arg) { //拉模型处理o //推模型处理arg }
非常简单!
下面用Java的观察者模式实现示例
1、目标类如下:
public class Panel extends java.util.Observable { // 表示图形,用于推模型 private List<PointOnScreen> graph = new ArrayList<PointOnScreen>(); private MouseEvent keyEvent; public MouseEvent getMouseEvent(){ return keyEvent; } // 添加鼠标事件 public void addKeyEvent(MouseEvent event) { //状态改变 this.keyEvent = event; //状态改变了,不可少 this.setChanged(); // 拉模型 this.notifyObservers(); // 推模型,处理鼠标事件 PointOnScreen point = new PointOnScreen(); point.setX(event.getX()); point.setY(event.getY()); graph.add(point); //推模型所传的对象 this.notifyObservers(graph); } }
2、监听器类如下:
//系统监听器 public class SystemListener implements java.util.Observer { // 在屏幕上将这个图形画出来 public void drawOnScreen(List<PointOnScreen> graph) { System.out.println(); System.out.println("刷新时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date())); for (PointOnScreen point : graph) { System.out.println("当前画到的点是——————(" + point.getX() + "," + point.getY() + ")"); } } // 表示图形 private List<PointOnScreen> graph = new ArrayList<PointOnScreen>(); @Override public void update(Observable o, Object arg) { if (o instanceof Panel) { MouseEvent event = ((Panel) o).getMouseEvent(); // 处理鼠标事件 PointOnScreen point = new PointOnScreen(); point.setX(event.getX()); point.setY(event.getY()); graph.add(point); drawOnScreen(graph); } //推模型 List<PointOnScreen> graph = (ArrayList<PointOnScreen>)arg; drawOnScreen(graph); } }