前言
一直很少动手写文档,觉得自己只是一个新手,对很多技术点或者概念理解的不透彻,没多少自信。二来,做程序遇到困难时,在论坛上找到的几乎都是通篇的代码,只能一行行解读代码,自己自圆其说。我不太喜欢这种模式,但是又不知道如何找到详细的资料,天天百度,费力不讨好。在图书馆找参考书,大多都是些基础的东西,也没什么帮助。
昨天,老师布置了一些作业,问我们应不应该强制?回来后我想了想,还是觉得不该强制,如果你爱JAVA,你自己会花时间在上面的。写文档倒是很有必要的。写文档,一来可以记录你的学习历程,二来可以暴露一些问题,如果你讲不清楚一个问题,很有可能时因为你对该问题的理解不透彻。
今天,我要叙述的内容是观察者模式,也称发布/订阅模式,监听器模式
观察者模式是软体设计模式的一种。在此种模式中,一个目标物件(被观察者)管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实作事件处理系统。
叙述该问题,通篇叙述不如通过实例来的易理解,不用实例,估计我也讲不清楚 。
例:给一个JFrame上的JTree添加节点,通过线程给JTree添加5个节点
1.直接调用模式
学习这么久,老师从没有讲过搭建模型,记得在写五子棋游戏的时候,那时候写的是郁闷的很,每添加一个功能时,都要修改好几个类。类与类的耦合非常高。
实现JFrame的JTree上添加节点:
思路:用线程来模拟给JTree添加节点,在线程的构造器中传入JTree对象。
/********************** *此类为消息类,用来作为树节点 */ public class Msg { private String text; public Msg(String text){ this.text = text; } //重写toString方法,设置节点显示内容 public String toString(){ return this.text; } }
线程类:
public class TestThread extends Thread{ private JTree tree; public TestThread(JTree tree){ //传入JTree对象 this.tree = tree ; } public void run(){ DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot(); for(int i=0;i<5;i++){ Msg msg = new Msg("第"+(i+1)+"个节点"); DefaultMutableTreeNode node = new DefaultMutableTreeNode(msg); //添加节点 root.add(node); //刷新树 javax.swing.SwingUtilities.updateComponentTreeUI(tree); //延时1S try{ Thread.sleep(1000); }catch(Exception e){ e.printStackTrace(); } } }
测试界面类:
public static void main(String[] args){ JFrame fr = new JFrame("Test_1"); fr.setSize(300, 300); fr.setDefaultCloseOperation(3); fr.setLayout(new FlowLayout(FlowLayout.LEFT)); DefaultMutableTreeNode root = new DefaultMutableTreeNode("根节点"); JTree tree = new JTree(root); fr.add(tree); new TestThread(tree).start(); fr.setVisible(true); }
总结1 :软件是应用户需要而编写的,肯定会经常修改。这种模式由于界面类和实现类之间的高耦合,要改界面类和实现
类都要改, 不利于程序的修改和扩展。
怎样才能避免这种牵一发动全身的问题呢?
想想,如果将具体对象间的依赖关系转化为接口实现的抽象关系会怎样,就可将界面类和实现类分别封装,使相互
独立,各自修改互补影响。对于本例,在线程中添加一个监听器队列,当产生事件时通知队列中的各监听器即可,这
需要有添加,移除,通知监听器的方法。
可将上面的代码修改一下
观察者模式
首先需要定义消息处理接口
public interface InListener { public void actionPerformed(Msg msg); }
根据需要编写实现类,本例中:
MyJTreeListener
public class MyJTreeListener implements InListener{ private JTree tree; private DefaultMutableTreeNode root; public MyJTreeListener(JTree tree){ this.tree = tree; root = (DefaultMutableTreeNode) tree.getModel().getRoot(); } public void actionPerformed(Msg msg) { DefaultMutableTreeNode node = new DefaultMutableTreeNode(msg); root.add(node); javax.swing.SwingUtilities.updateComponentTreeUI(tree); } }
线程类:
public class TestThread extends Thread{ //监听器队列 private ArrayList<InListener> list ; public TestThread(){ //初始化监听器队列 list = new ArrayList(); } public void run(){ for(int i=0;i<5;i++){ Msg msg = new Msg("第"+(i+1)+"条消息"); //通知各监听器 this.fireInListener(msg); //延时1S try{ Thread.sleep(1000); }catch(Exception e){ e.printStackTrace(); } } } /***********************8 * 通知所有消息监听器 * @param msg:消息 */ public void fireInListener(Msg msg){ for(int i=0;i<list.size();i++){ InListener listener = list.get(i); listener.actionPerformed(msg); } } /*************************** * 添加事件监听器 * @param l:要添加的监听器 * @return:添加结果 */ public boolean addListener(InListener l){ return list.add(l); } /**************************** * 移除事件监听器 * @param l:要移除的监听器 * @return:移除结果 */ public boolean removeListener(InListener l){ return list.remove(l); }
测试:
public static void main(String[] args){ JFrame fr = new JFrame("Test_2"); fr.setSize(300, 300); fr.setDefaultCloseOperation(3); fr.setLayout(new FlowLayout(FlowLayout.LEFT)); DefaultMutableTreeNode root = new DefaultMutableTreeNode("根节点"); JTree tree = new JTree(root); fr.add(tree); TestThread nt = new TestThread(); //注册监听器 nt.addListener(new MyJTreeListener(tree)); nt.start(); fr.setVisible(true); }
总结2: 这样,线程对象和具体的事件处理对象就可以相互独立的修改 但实际上这是一种具体对抽象的关系,可以进一步将事件产生类(本例中为线程)进行抽象,变为抽象对抽象的关系,毕竟现实中的事件产生可以有很多种情况,往往风马牛不相及。
只需再定义一个事件产生接口即可:
InEventSource
public interface InEventSource { public ArrayList<InListener> list = new ArrayList(); public boolean addListener(InListener l); public boolean removeListener(InListener l); public void fireInListener(Msg msg); }
线程类:
public class TestThread extends Thread implements InEventSource{ public void run(){ for(int j=0;j<5;j++){ Msg msg = new Msg("第"+(j+1)+"条消息"); this.fireInListener(msg); //延时1S try{ Thread.sleep(1000); }catch(Exception e){ e.printStackTrace(); } } } public void fireInListener(Msg msg){ for(int i=0;i<list.size();i++){ InListener listener = list.get(i); listener.actionPerformed(msg); } } public boolean addListener(InListener l){ return list.add(l); } public boolean removeListener(InListener l){ return list.remove(l); }
测试不变
总结3: 观察者模式所进行的工作就是解耦,就是将具体的对象之间的关系进行抽象,变为抽象对抽象的关系,降低具体对象间的依赖性,从而实现良好的修改,扩充性。其实,就是让我们懂得封装的重要性,教我们如何封装。
什么时候应该采用观察者模式
我觉得,当一个对象改变时,同时要改变其他对象,而且不知道要改变多少其他对象时,应该考虑采用观察者模式。
由于我的水平有限,讲的不是很清楚,如果有错的,或是不好的地方,欢迎指出批评。
附上 《大话设计模式》下载链接 书中第14章有很生动的讲解