观察者模式 Observer的定义
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。
这个主题对象在状态上发生变化时,会通知所有观察者对象,让它们能够自动更新自己。
第一部分
这里有一个例子,是马士兵老师在讲解观察者模式的时候给出的例子,个人认为对理解观察者模式有很大的用处,自己查到的一些博文也写得很好,但是太过于一板一眼了,不便于去理解。具体的例子是这样的:一个小孩在睡觉,当小孩醒过来之后,爸爸要feed,爷爷要哄哄抱抱,小狗汪汪叫。在这里这个睡觉的小孩就是被观察的对象,后面三个对象就是观察者,小孩的状态发生改变的时候,就相当于一个事件被触发了,观察者(或者应该叫做监听者)会做出相应的动作。下面是具体的是代码实现。
第一步:我们定义被观察对象
1 class Child implements Runnable { 2 //用List来存放不同的监听 3 private List<WakeUpListener> wakeUpListeners = new ArrayList<WakeUpListener>(); 4 5 //List中添加监听的操作 6 public void addWakenUpListener(WakeUpListener l) { 7 wakeUpListeners.add(l); 8 } 9 10 //被观察者小孩的状态发生改变,则会通知观察者,使其执行各自的performAction方法 11 public void wakeUp() { 12 for (int i = 0; i < wakeUpListeners.size(); i++) { 13 WakeUpListener l = wakeUpListeners.get(i); 14 // 15 l.performAction(new WakeUpEvent(System.currentTimeMillis(), "沙发上", 16 this)); 17 } 18 } 19 20 //监听线程的run() 21 public void run() { 22 try { 23 Thread.sleep(1000); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 this.wakeUp(); 28 } 29 30 }
第二步:给出监听接口,具体的观察者都去实现这个接口,具体的观察者复写接口的performAction方法,小孩的状态发生变化,做出响应
1 interface WakeUpListener { 2 public void performAction(WakeUpEvent wakeUpEvent); 3 }
第三步:定义具体的观察者
1 /* 2 * 具体观察者ConcreteObserver,被观察的对象child的状态发生变化这一事件会触发观察者的performAction方法,针对被观察者的变化做出反应 3 */ 4 //具体观察者一 5 class Dad implements WakeUpListener { 6 7 public void performAction(WakeUpEvent wakeUpEvent) { 8 System.out.println("..feed.."); 9 } 10 11 } 12 //具体观察者二 13 class Dog implements WakeUpListener { 14 15 public void performAction(WakeUpEvent wakeUpEvent) { 16 System.out.println("..汪汪.."); 17 } 18 19 } 20 //具体观察者三 21 class Grand implements WakeUpListener { 22 23 public void performAction(WakeUpEvent wakeUpEvent) { 24 System.out.println("..hug.."); 25 } 26 27 }
第四步:定义事件类Event,Event事件类,将观察者状态的改变封装成Event类,不同的状态对应不同的事件。在我们的例子中,小孩的状态发生改变,他的观察者Dad、Dog、Grand会针对此事件做出反应;
1 class WakeUpEvent { 2 //描述了事件的一些基本的信息:时间+地点+被观察对象 3 private long time; 4 private String location; 5 private Child child; 6 7 public WakeUpEvent(long time, String location, Child child) { 8 super(); 9 this.time = time; 10 this.location = location; 11 this.child = child; 12 } 13 14 public long getTime() { 15 return time; 16 } 17 18 public void setTime(long time) { 19 this.time = time; 20 } 21 22 public String getLocation() { 23 return location; 24 } 25 26 public void setLocation(String location) { 27 this.location = location; 28 } 29 30 public Child getChild() { 31 return child; 32 } 33 34 public void setChild(Child child) { 35 this.child = child; 36 } 37 38 }
第五步:下面的observers是我们的配置文件的文件名,尽量将这些动作的实现对客户端隐藏,用户不需要明白加载读取配合文件的操作,在做代码设计的时候要始终坚持这一原则。
1 try { 2 props.load(ObserveTest.class.getClassLoader().getResourceAsStream( 3 "Observers.properties")); 4 } catch (IOException e) { 5 e.printStackTrace(); 6 }
我们在这里将读取配置文件的动作封装在类中:
1 //这里将读取配置文件Observers.properties的操作封装在类中,使用静态代码块的形式,在类加载的时候将配置文件加载进内存 2 //提高代码的灵活行,避免反复的执行加载配置文件的操作 3 class PropertyMgr { 4 // 重要的思想:缓存 5 // 单例初步以及缓存:把硬盘上的内容缓存到内存上 6 // 缓存的策略:访问最多的文件进行缓存 7 private static Properties props = new Properties(); 8 // 这里使用了静态代码块,类加载的时候初始化一次 9 static { 10 try { 11 props.load(ObserveTest.class.getClassLoader().getResourceAsStream( 12 "Observers.properties")); 13 } catch (IOException e) { 14 e.printStackTrace(); 15 } 16 } 17 //定义成静态static方法,方便在类外直接访问 18 public static String getProperty(String key) throws IOException { 19 return props.getProperty(key); 20 21 } 22 }
最后一步:测试一下我们的程序,这里我给出完整的代码,方便读者的调试验证(这里附上我们的配置文件Observers.properties),只是一个简单的键值对应关系:observers=Grand,Dog,Dad
1 import java.io.IOException; 2 import java.util.ArrayList; 3 import java.util.List; 4 import java.util.Properties; 5 6 import org.omg.CORBA.PRIVATE_MEMBER; 7 8 //Event事件类,将观察者状态的改变封装成Event类,不同的状态对应不同的事件 9 //在我们的例子中,小孩的状态发生改变,他的观察者Dad、Dog、Grand会针对此事件做出反应 10 class WakeUpEvent { 11 //描述了事件的一些基本的信息:时间+地点+被观察对象 12 private long time; 13 private String location; 14 private Child child; 15 16 public WakeUpEvent(long time, String location, Child child) { 17 super(); 18 this.time = time; 19 this.location = location; 20 this.child = child; 21 } 22 23 public long getTime() { 24 return time; 25 } 26 27 public void setTime(long time) { 28 this.time = time; 29 } 30 31 public String getLocation() { 32 return location; 33 } 34 35 public void setLocation(String location) { 36 this.location = location; 37 } 38 39 public Child getChild() { 40 return child; 41 } 42 43 public void setChild(Child child) { 44 this.child = child; 45 } 46 47 } 48 49 //观察者模式中的Subject(目标),被观察对象 50 51 class Child implements Runnable { 52 //同List来存放不同的监听 53 private List<WakeUpListener> wakeUpListeners = new ArrayList<WakeUpListener>(); 54 55 //List中添加监听的操作 56 public void addWakenUpListener(WakeUpListener l) { 57 wakeUpListeners.add(l); 58 } 59 60 //被观察者小孩的状态发生改变,则会通知观察者,使其执行各自的performAction方法 61 public void wakeUp() { 62 for (int i = 0; i < wakeUpListeners.size(); i++) { 63 WakeUpListener l = wakeUpListeners.get(i); 64 // 65 l.performAction(new WakeUpEvent(System.currentTimeMillis(), "沙发上", 66 this)); 67 } 68 } 69 70 //监听线程的run() 71 public void run() { 72 try { 73 Thread.sleep(1000); 74 } catch (InterruptedException e) { 75 e.printStackTrace(); 76 } 77 this.wakeUp(); 78 } 79 80 } 81 /* 82 * 具体观察者ConcreteObserver,被观察的对象child的状态发生变化这一事件会触发观察者的performAction方法,针对被观察者的变化做出反应 83 */ 84 //具体观察者一 85 class Dad implements WakeUpListener { 86 87 public void performAction(WakeUpEvent wakeUpEvent) { 88 System.out.println("..feed.."); 89 } 90 91 } 92 //具体观察者二 93 class Dog implements WakeUpListener { 94 95 public void performAction(WakeUpEvent wakeUpEvent) { 96 System.out.println("..汪汪.."); 97 } 98 99 } 100 //具体观察者三 101 class Grand implements WakeUpListener { 102 103 public void performAction(WakeUpEvent wakeUpEvent) { 104 System.out.println("..hug.."); 105 } 106 107 } 108 //抽象的观察Observer 109 interface WakeUpListener { 110 public void performAction(WakeUpEvent wakeUpEvent); 111 } 112 113 public class ObserveTest { 114 115 /** 116 * @param args 117 * @throws IOException 118 * @throws ClassNotFoundException 119 * @throws IllegalAccessException 120 * @throws InstantiationException 121 */ 122 public static void main(String[] args) throws Exception { 123 //读取配置文件的操作改成了静态方法,使用的时候直接调用,下面的observers是我们的配置文件的文件名 124 String observers[] = PropertyMgr.getProperty("observers").split(","); 125 Child child = new Child(); 126 for (String s : observers) { 127 child.addWakenUpListener((WakeUpListener) Class.forName(s) 128 .newInstance()); 129 } 130 new Thread(child).start(); 131 } 132 } 133 134 //这里将读取配置文件Observers.properties的操作封装在类中,使用静态代码块的形式,在类加载的时候将配置文件加载进内存 135 //提高代码的灵活行,避免反复的执行加载配置文件的操作 136 class PropertyMgr { 137 // 重要的思想:缓存 138 // 单例初步以及缓存:把硬盘上的内容缓存到内存上 139 // 缓存的策略:访问最多的文件进行缓存 140 private static Properties props = new Properties(); 141 // 这里使用了静态代码块,类加载的时候初始化一次 142 static { 143 try { 144 props.load(ObserveTest.class.getClassLoader().getResourceAsStream( 145 "Observers.properties")); 146 } catch (IOException e) { 147 e.printStackTrace(); 148 } 149 } 150 //定义成静态static方法,方便在类外直接访问 151 public static String getProperty(String key) throws IOException { 152 return props.getProperty(key); 153 154 } 155 }
运行结果:
..hug..
..汪汪..
..feed..
第二部分
面试的过程可能会问到什么是观察者模式,其实这个时候不要给他们说什么太过于理论性的东西,举例子最方便不过了。观察者模式在java中的运用其实挺多的,比如说AWT和Swing中的监听机制用到的就是观察者模式,下面我们就来模拟一下看看监听机制是如何运作的。【注意】,代码中用到的类和方法都是我们自己定义的,不是调用API中的类和方法。
第一步:给出被监听对象:我们定义的一个按钮button
1 //首先定义一个按钮 2 class Button { 3 //创建一个具体的事件对象 4 ActionEvent e = new ActionEvent(System.currentTimeMillis(), this); 5 //List存储不同的监听者对象 6 private List<ActionListener> actionListeners = new ArrayList<ActionListener>(); 7 8 //button按钮被按下时所触发的动作 9 public void buttonPressed() { 10 for (int i = 0; i < actionListeners.size(); i++) { 11 ActionListener l = actionListeners.get(i); 12 //按下button,监听者会做出相应的动作 13 l.actionPerformed(e); 14 } 15 } 16 17 //add添加监听者的动作 18 public void addActionListener(ActionListener l) { 19 actionListeners.add(l); 20 } 21 }
第二步:定义监听接口,具体的监听者去实现这个接口“
1 interface ActionListener { 2 public void actionPerformed(ActionEvent e); 3 }
第三步:具体的监听者
在这里我们定义了两个监听者类
1 class MyActionListener implements ActionListener { 2 3 public void actionPerformed(ActionEvent E) { 4 System.out.println("button pressed"); 5 } 6 } 7 8 class MyActionListener2 implements ActionListener { 9 10 public void actionPerformed(ActionEvent E) { 11 System.out.println("button pressed2"); 12 } 13 }
第四步:定义监听事件Event,时间对象包括:时间的发生时间when+事件源
1 class ActionEvent { 2 long when; 3 Object source; 4 5 public ActionEvent(long when, Object source) { 6 super(); 7 this.when = when; 8 } 9 10 public long getWhen() { 11 return when; 12 } 13 14 public Object getSource() { 15 return source; 16 } 17 }
第五步:给出测试代码
1 public class Test { 2 public static void main(String args[]) { 3 4 Button b = new Button(); 5 b.addActionListener(new MyActionListener()); 6 b.addActionListener(new MyActionListener2()); 7 b.buttonPressed(); 8 } 9 }
运行结果:
button pressed
button pressed2
最后给出完整代码方便理解调试:
1 package com.observer.awt; 2 3 import java.util.ArrayList; 4 5 import java.util.List; 6 7 public class Test { 8 public static void main(String args[]) { 9 10 Button b = new Button(); 11 b.addActionListener(new MyActionListener()); 12 b.addActionListener(new MyActionListener2()); 13 b.buttonPressed(); 14 } 15 } 16 17 //首先定义一个按钮 18 class Button { 19 //创建一个具体的事件对象 20 ActionEvent e = new ActionEvent(System.currentTimeMillis(), this); 21 //List存储不同的监听者对象 22 private List<ActionListener> actionListeners = new ArrayList<ActionListener>(); 23 24 //button按钮被按下时所触发的动作 25 public void buttonPressed() { 26 for (int i = 0; i < actionListeners.size(); i++) { 27 ActionListener l = actionListeners.get(i); 28 //按下button,监听者会做出相应的动作 29 l.actionPerformed(e); 30 } 31 } 32 33 //add添加监听者的动作 34 public void addActionListener(ActionListener l) { 35 actionListeners.add(l); 36 } 37 } 38 39 class MyActionListener implements ActionListener { 40 41 public void actionPerformed(ActionEvent E) { 42 System.out.println("button pressed"); 43 } 44 } 45 46 class MyActionListener2 implements ActionListener { 47 48 public void actionPerformed(ActionEvent E) { 49 System.out.println("button pressed2"); 50 } 51 } 52 53 interface ActionListener { 54 public void actionPerformed(ActionEvent e); 55 } 56 57 class ActionEvent { 58 long when; 59 Object source; 60 61 public ActionEvent(long when, Object source) { 62 super(); 63 this.when = when; 64 } 65 66 public long getWhen() { 67 return when; 68 } 69 70 public Object getSource() { 71 return source; 72 } 73 }
第三部分:我们在第二步给出了我们自己模拟的按钮按下触发相应动作的过程,第三部分给出AWT中,调用API中封装的一些已经实现好的类和方法,和第二步完成的是相同的动作。
1 package com.observer.awt; 2 3 import java.awt.Button; 4 import java.awt.Frame; 5 import java.awt.event.ActionEvent; 6 import java.awt.event.ActionListener; 7 import java.awt.event.WindowAdapter; 8 9 public class TestFram extends Frame { 10 public void lanch() { 11 // 定义一个按钮,按钮显示的信息是"press me" 12 Button b = new Button("press me"); 13 // 添加具体监听者 14 b.addActionListener(new MyActionListener()); 15 b.addActionListener(new MyActionListener2()); 16 // add方法将我们定义的button加入到Frame框架中 17 this.add(b); 18 // pack(),调整窗体的大小,里面可以添加参数 19 this.pack(); 20 // 我们定义的TestFrame框架添加窗口监听 21 this.addWindowListener(new WindowAdapter() { 22 }); 23 // 使窗体可见 24 this.setVisible(true); 25 } 26 27 public static void main(String args[]) { 28 // 调用TestFram中的lanch方法,在Frame框架中定义一个按钮 29 new TestFram().lanch(); 30 } 31 32 // 具体的监听者 33 private class MyActionListener implements ActionListener { 34 35 public void actionPerformed(ActionEvent e) { 36 // 监听者1观察到按钮被按下,做出反应 37 System.out.println("button pressed"); 38 } 39 } 40 41 private class MyActionListener2 implements ActionListener { 42 43 public void actionPerformed(ActionEvent e) { 44 // 监听者2观察到按钮被按下,做出反应 45 System.out.println("button pressed 2!"); 46 } 47 } 48 }