Java设计模式系列之观察者模式
观察者模式 Observer的定义
观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。
这个主题对象在状态上发生变化时,会通知所有观察者对象,让它们能够自动更新自己。
第一部分
这里有一个例子,是马士兵老师在讲解观察者模式的时候给出的例子,个人认为对理解观察者模式有很大的用处,自己查到的一些博文也写得很好,但是太过于一板一眼了,不便于去理解。具体的例子是这样的:一个小孩在睡觉,当小孩醒过来之后,爸爸要feed,爷爷要哄哄抱抱,小狗汪汪叫。在这里这个睡觉的小孩就是被观察的对象,后面三个对象就是观察者,小孩的状态发生改变的时候,就相当于一个事件被触发了,观察者(或者应该叫做监听者)会做出相应的动作。下面是具体的是代码实现。
第一步:我们定义被观察对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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方法,小孩的状态发生变化,做出响应
1 2 3 | interface WakeUpListener { public void performAction(WakeUpEvent wakeUpEvent); } |
第三步:定义具体的观察者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /* * 具体观察者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会针对此事件做出反应;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | 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是我们的配置文件的文件名,尽量将这些动作的实现对客户端隐藏,用户不需要明白加载读取配合文件的操作,在做代码设计的时候要始终坚持这一原则。
1 2 3 4 5 6 | try { props.load(ObserveTest. class .getClassLoader().getResourceAsStream( "Observers.properties" )); } catch (IOException e) { e.printStackTrace(); } |
我们在这里将读取配置文件的动作封装在类中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //这里将读取配置文件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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | 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); } } |
运行结果:
1 2 3 | ..hug.. ..汪汪.. ..feed.. |
第二部分
面试的过程可能会问到什么是观察者模式,其实这个时候不要给他们说什么太过于理论性的东西,举例子最方便不过了。观察者模式在java中的运用其实挺多的,比如说AWT和Swing中的监听机制用到的就是观察者模式,下面我们就来模拟一下看看监听机制是如何运作的。【注意】,代码中用到的类和方法都是我们自己定义的,不是调用API中的类和方法。
第一步:给出被监听对象:我们定义的一个按钮button
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | //首先定义一个按钮 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); } } |
第二步:定义监听接口,具体的监听者去实现这个接口
1 2 3 | interface ActionListener { public void actionPerformed(ActionEvent e); } |
第三步:具体的监听者
在这里我们定义了两个监听者类
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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+事件源
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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; } } |
第五步:给出测试代码
1 2 3 4 5 6 7 8 9 | public class Test { public static void main(String args[]) { Button b = new Button(); b.addActionListener( new MyActionListener()); b.addActionListener( new MyActionListener2()); b.buttonPressed(); } } |
运行结果:
1 2 | button pressed button pressed2 |
最后给出完整代码方便理解调试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | 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中封装的一些已经实现好的类和方法,和第二步完成的是相同的动作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | 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!" ); } } } |
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步