观察者模式 Observer的定义
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。
这个主题对象在状态上发生变化时,会通知所有观察者对象,让它们能够自动更新自己。
第一部分
这里有一个例子,是马士兵老师在讲解观察者模式的时候给出的例子,个人认为对理解观察者模式有很大的用处,自己查到的一些博文也写得很好,但是太过于一板一眼了,不便于去理解。具体的例子是这样的:一个小孩在睡觉,当小孩醒过来之后,爸爸要feed,爷爷要哄哄抱抱,小狗汪汪叫。在这里这个睡觉的小孩就是被观察的对象,后面三个对象就是观察者,小孩的状态发生改变的时候,就相当于一个事件被触发了,观察者(或者应该叫做监听者)会做出相应的动作。下面是具体的是代码实现。
第一步:我们定义被观察对象
class Child implements Runnable { //用List来存放不同的监听 private List<WakeUpListener> wakeUpListeners = new ArrayList<WakeUpListener>(); //List中添加监听的操作 public void addWakenUpListener(WakeUpListener l) { wakeUpListeners.add(l); } //被观察者小孩的状态发生改变,则会通知观察者,使其执行各自的performAction方法 public void wakeUp() { for (int i = 0; i < wakeUpListeners.size(); i++) { WakeUpListener l = wakeUpListeners.get(i); // l.performAction(new WakeUpEvent(System.currentTimeMillis(), "沙发上", this)); } } //监听线程的run() public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.wakeUp(); } }
第二步:给出监听接口,具体的观察者都去实现这个接口,具体的观察者复写接口的performAction方法,小孩的状态发生变化,做出响应
interface WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent); }
第三步:定义具体的观察者
/* * 具体观察者ConcreteObserver,被观察的对象child的状态发生变化这一事件会触发观察者的performAction方法,针对被观察者的变化做出反应 */ //具体观察者一 class Dad implements WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent) { System.out.println("..feed.."); } } //具体观察者二 class Dog implements WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent) { System.out.println("..汪汪.."); } } //具体观察者三 class Grand implements WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent) { System.out.println("..hug.."); } }
第四步:定义事件类Event,Event事件类,将观察者状态的改变封装成Event类,不同的状态对应不同的事件。在我们的例子中,小孩的状态发生改变,他的观察者Dad、Dog、Grand会针对此事件做出反应;
class WakeUpEvent { //描述了事件的一些基本的信息:时间+地点+被观察对象 private long time; private String location; private Child child; public WakeUpEvent(long time, String location, Child child) { super(); this.time = time; this.location = location; this.child = child; } public long getTime() { return time; } public void setTime(long time) { this.time = time; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public Child getChild() { return child; } public void setChild(Child child) { this.child = child; } }
第五步:下面的observers是我们的配置文件的文件名,尽量将这些动作的实现对客户端隐藏,用户不需要明白加载读取配合文件的操作,在做代码设计的时候要始终坚持这一原则。
try { props.load(ObserveTest.class.getClassLoader().getResourceAsStream( "Observers.properties")); } catch (IOException e) { e.printStackTrace(); }
我们在这里将读取配置文件的动作封装在类中:
//这里将读取配置文件Observers.properties的操作封装在类中,使用静态代码块的形式,在类加载的时候将配置文件加载进内存 //提高代码的灵活行,避免反复的执行加载配置文件的操作 class PropertyMgr { // 重要的思想:缓存 // 单例初步以及缓存:把硬盘上的内容缓存到内存上 // 缓存的策略:访问最多的文件进行缓存 private static Properties props = new Properties(); // 这里使用了静态代码块,类加载的时候初始化一次 static { try { props.load(ObserveTest.class.getClassLoader().getResourceAsStream( "Observers.properties")); } catch (IOException e) { e.printStackTrace(); } } //定义成静态static方法,方便在类外直接访问 public static String getProperty(String key) throws IOException { return props.getProperty(key); } }
最后一步:测试一下我们的程序,这里我给出完整的代码,方便读者的调试验证(这里附上我们的配置文件Observers.properties),只是一个简单的键值对应关系:observers=Grand,Dog,Dad
import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.omg.CORBA.PRIVATE_MEMBER; //Event事件类,将观察者状态的改变封装成Event类,不同的状态对应不同的事件 //在我们的例子中,小孩的状态发生改变,他的观察者Dad、Dog、Grand会针对此事件做出反应 class WakeUpEvent { //描述了事件的一些基本的信息:时间+地点+被观察对象 private long time; private String location; private Child child; public WakeUpEvent(long time, String location, Child child) { super(); this.time = time; this.location = location; this.child = child; } public long getTime() { return time; } public void setTime(long time) { this.time = time; } public String getLocation() { return location; } public void setLocation(String location) { this.location = location; } public Child getChild() { return child; } public void setChild(Child child) { this.child = child; } } //观察者模式中的Subject(目标),被观察对象 class Child implements Runnable { //同List来存放不同的监听 private List<WakeUpListener> wakeUpListeners = new ArrayList<WakeUpListener>(); //List中添加监听的操作 public void addWakenUpListener(WakeUpListener l) { wakeUpListeners.add(l); } //被观察者小孩的状态发生改变,则会通知观察者,使其执行各自的performAction方法 public void wakeUp() { for (int i = 0; i < wakeUpListeners.size(); i++) { WakeUpListener l = wakeUpListeners.get(i); // l.performAction(new WakeUpEvent(System.currentTimeMillis(), "沙发上", this)); } } //监听线程的run() public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } this.wakeUp(); } } /* * 具体观察者ConcreteObserver,被观察的对象child的状态发生变化这一事件会触发观察者的performAction方法,针对被观察者的变化做出反应 */ //具体观察者一 class Dad implements WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent) { System.out.println("..feed.."); } } //具体观察者二 class Dog implements WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent) { System.out.println("..汪汪.."); } } //具体观察者三 class Grand implements WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent) { System.out.println("..hug.."); } } //抽象的观察Observer interface WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent); } public class ObserveTest { /** * @param args * @throws IOException * @throws ClassNotFoundException * @throws IllegalAccessException * @throws InstantiationException */ public static void main(String[] args) throws Exception { //读取配置文件的操作改成了静态方法,使用的时候直接调用,下面的observers是我们的配置文件的文件名 String observers[] = PropertyMgr.getProperty("observers").split(","); Child child = new Child(); for (String s : observers) { child.addWakenUpListener((WakeUpListener) Class.forName(s) .newInstance()); } new Thread(child).start(); } } //这里将读取配置文件Observers.properties的操作封装在类中,使用静态代码块的形式,在类加载的时候将配置文件加载进内存 //提高代码的灵活行,避免反复的执行加载配置文件的操作 class PropertyMgr { // 重要的思想:缓存 // 单例初步以及缓存:把硬盘上的内容缓存到内存上 // 缓存的策略:访问最多的文件进行缓存 private static Properties props = new Properties(); // 这里使用了静态代码块,类加载的时候初始化一次 static { try { props.load(ObserveTest.class.getClassLoader().getResourceAsStream( "Observers.properties")); } catch (IOException e) { e.printStackTrace(); } } //定义成静态static方法,方便在类外直接访问 public static String getProperty(String key) throws IOException { return props.getProperty(key); } }
运行结果:
..hug.. ..汪汪.. ..feed..
第二部分
面试的过程可能会问到什么是观察者模式,其实这个时候不要给他们说什么太过于理论性的东西,举例子最方便不过了。观察者模式在java中的运用其实挺多的,比如说AWT和Swing中的监听机制用到的就是观察者模式,下面我们就来模拟一下看看监听机制是如何运作的。【注意】,代码中用到的类和方法都是我们自己定义的,不是调用API中的类和方法。
第一步:给出被监听对象:我们定义的一个按钮button
//首先定义一个按钮 class Button { //创建一个具体的事件对象 ActionEvent e = new ActionEvent(System.currentTimeMillis(), this); //List存储不同的监听者对象 private List<ActionListener> actionListeners = new ArrayList<ActionListener>(); //button按钮被按下时所触发的动作 public void buttonPressed() { for (int i = 0; i < actionListeners.size(); i++) { ActionListener l = actionListeners.get(i); //按下button,监听者会做出相应的动作 l.actionPerformed(e); } } //add添加监听者的动作 public void addActionListener(ActionListener l) { actionListeners.add(l); } }
第二步:定义监听接口,具体的监听者去实现这个接口
interface ActionListener { public void actionPerformed(ActionEvent e); }
第三步:具体的监听者
在这里我们定义了两个监听者类
class MyActionListener implements ActionListener { public void actionPerformed(ActionEvent E) { System.out.println("button pressed"); } } class MyActionListener2 implements ActionListener { public void actionPerformed(ActionEvent E) { System.out.println("button pressed2"); } }
第四步:定义监听事件Event,时间对象包括:时间的发生时间when+事件源
class ActionEvent { long when; Object source; public ActionEvent(long when, Object source) { super(); this.when = when; } public long getWhen() { return when; } public Object getSource() { return source; } }
第五步:给出测试代码
public class Test { public static void main(String args[]) { Button b = new Button(); b.addActionListener(new MyActionListener()); b.addActionListener(new MyActionListener2()); b.buttonPressed(); } }
运行结果:
button pressed button pressed2
最后给出完整代码方便理解调试:
package com.observer.awt; import java.util.ArrayList; import java.util.List; public class Test { public static void main(String args[]) { Button b = new Button(); b.addActionListener(new MyActionListener()); b.addActionListener(new MyActionListener2()); b.buttonPressed(); } } //首先定义一个按钮 class Button { //创建一个具体的事件对象 ActionEvent e = new ActionEvent(System.currentTimeMillis(), this); //List存储不同的监听者对象 private List<ActionListener> actionListeners = new ArrayList<ActionListener>(); //button按钮被按下时所触发的动作 public void buttonPressed() { for (int i = 0; i < actionListeners.size(); i++) { ActionListener l = actionListeners.get(i); //按下button,监听者会做出相应的动作 l.actionPerformed(e); } } //add添加监听者的动作 public void addActionListener(ActionListener l) { actionListeners.add(l); } } class MyActionListener implements ActionListener { public void actionPerformed(ActionEvent E) { System.out.println("button pressed"); } } class MyActionListener2 implements ActionListener { public void actionPerformed(ActionEvent E) { System.out.println("button pressed2"); } } interface ActionListener { public void actionPerformed(ActionEvent e); } class ActionEvent { long when; Object source; public ActionEvent(long when, Object source) { super(); this.when = when; } public long getWhen() { return when; } public Object getSource() { return source; } }
第三部分:我们在第二步给出了我们自己模拟的按钮按下触发相应动作的过程,第三部分给出AWT中,调用API中封装的一些已经实现好的类和方法,和第二步完成的是相同的动作。
package com.observer.awt; import java.awt.Button; import java.awt.Frame; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; public class TestFram extends Frame { public void lanch() { // 定义一个按钮,按钮显示的信息是"press me" Button b = new Button("press me"); // 添加具体监听者 b.addActionListener(new MyActionListener()); b.addActionListener(new MyActionListener2()); // add方法将我们定义的button加入到Frame框架中 this.add(b); // pack(),调整窗体的大小,里面可以添加参数 this.pack(); // 我们定义的TestFrame框架添加窗口监听 this.addWindowListener(new WindowAdapter() { }); // 使窗体可见 this.setVisible(true); } public static void main(String args[]) { // 调用TestFram中的lanch方法,在Frame框架中定义一个按钮 new TestFram().lanch(); } // 具体的监听者 private class MyActionListener implements ActionListener { public void actionPerformed(ActionEvent e) { // 监听者1观察到按钮被按下,做出反应 System.out.println("button pressed"); } } private class MyActionListener2 implements ActionListener { public void actionPerformed(ActionEvent e) { // 监听者2观察到按钮被按下,做出反应 System.out.println("button pressed 2!"); } } }