问题的提出 以前做一个界面的时候常常会遇到这样的尴尬情况希望保留各个独立的组件(类)但又希望它们之间能够相互通信譬如Windows中的Explorer我们希望鼠标点击左边是树型目录的一个节点右边的文件浏览能及时列出该节点目录下的文件和子目录类似这样一个简单的应用如果只有一个类继承JFrame而树型组件和浏览文件的面板作为成员就像 public class MainFrame extends JFrame { JPanel treePanel; JTree tree; JPanel filePanel;
} 这样当然容易在两者之间传递消息但是可扩展性较差通常容易想到的是两种办法在一个组件里保留另一个组件类型的成员初始化时作为参数传入引用比如 class TreePanel extends JPanel { JTree tree;
} class FilePanel extends JPanel { public FilePanel(JTree tree){}
} 或者将一个组件线程化不停地监听另一个组件的变化然后作出相应的反映比如 class TreePanel extends JPanel { JTree tree;
} class FilePanel extends JPanel implements Runnable { public void run() { while (true) { //监听tree的变化 }
}
} 这样确实可以达到我们的目的但是第一种方案显然不利于松散耦合第二种方案比较占用系统资源通过学习设计模式我们发现可以用Observer模式来解决这个问题 Observer模式 设计模式分为创建型结构型和行为型其中行为型模式专门处理对象间通信指定交互方式等Observer模式就是属于行为型的一种设计模式按照四人帮(Gang of Four)在Design Patterns里的定义Observer模式定义对象间的一种一对多的依赖关系当一个对象的状态发生改变时 所有依赖于它的对象都得到通知并被自动更新这个描述正好符合我们对组件通信问题的需求让我们先看看Observer模式的结构 其中各元素的含义如下 Subject被观察的目标的抽象接口它提供对观察者(Observer)的注册注销服务Notify方法通知Observer目标发生改变 Object观察者的抽象接口Update方法是当得到Subject状态变化的通知后所要采取的动作 ConcreteSubjectSubject的具体实现 ConcreteObserverObserver的具体实现 Observer模式在实现MVC结构时非常有用为数据和数据表示解耦合 Java中的Observer模式Observer和Observable 在大致了解了Observer模式的描述之后现在我们更为关心的是它在Java中是如何应用的幸运的是自从JDK 起就有了专门处理这种应用的API这就是Observer接口和Observable类它们是属于javautil包的一部分看来Java的开发者们真是深谙设计模式的精髓而Java的确是为了真正的面向对象而生的呵呵! 这里的Observer和Observable分别对应设计模式中的Observer和Subject对比一下它们定义的方法痕迹还是相当明显的 Observer的方法 update(Observable subject Object arg) 监控subject当subject对象状态发生变化时Observer会有什么响应arg是传递给Observable的notifyObservers方法的参数 Observable的方法 addObserver(Observer observer) observer向该subject注册自己 hasChanged() 检查该subject状态是否发生变化 setChanged() 设置该subject的状态为已变化 notifyObservers() 通知observer该subject状态发生变化 Observer模式在Java GUI事件模型中应用 其实在AWT/Swing事件模型中用到了好几种设计模式以前的JDK AWT使用的是基于继承的事件模型在该模型Component类中定义了一系列事件处理方法如handleEventmouseDownmouseUp等等我们对事件的响应是通过对组件类继承并覆盖相应的事件处理方法的手段来实现这种模型有很多缺点事件的处理不应当由事件产生者负责而且根据设计模式一书中的原则继承通常被认为是对封装性的破坏父子类之间的紧密耦合关系降低了灵活性同时继承容易导致家族树规模的庞大这些都不利于组件可重用 JDK 以后新的事件模型是被成为基于授权的事件模型也就是我们现在所熟悉的Listener模型事件的处理不再由产生事件的对象负责而由Listener负责尤其在Swing组件中设计MVC结构时用到了Observer模式众所周知MVC表示模型-视图-控制器即数据-表示逻辑-操作其中数据可以对应多种表示这样视图就处在了observer的地位而model则是subject 简单的例子 回到本文一开始的那个Explorer的例子我们考虑做一个简单的图片浏览器使树型选择组件和图片浏览面板在两个不同的类中其中图片浏览面板根据所选择的树的节点显示相应的图片所以图片浏览面板是一个observer树是subject由于Java单根继承的原因我们不能同时继承JPanel和Observable但可以用对象的组合把一个subject放到我们的类当中并通过TreeSelectionListener触发subject的setChanged方法并通过notifyObservers方法通知observer 例子代码如下 //LeftPaneljava package comjunglefordtest; import javaawtBorderLayout; import javaxswing*; import javaxswingeventTreeSelectionListener; import javaxswingeventTreeSelectionEvent; import javaxswingtreeDefaultMutableTreeNode; import javautilObservable; import javautilObserver; public final class LeftPanel extends JPanel {// 把树型选择视图布局在左边 private JTree tree;// 树型选择视图 private JScrollPane scroll;// 让视图可滚动 private DefaultMutableTreeNode root node node;// 根节点及两个叶子 private Sensor sensor;// sensor是一个Observable由于只能单根继承所以作为组合成员 private String file;// 图片文件名与RightPanel通信的内容 public LeftPanel(Observer observer) { file = ; sensor = new Sensor(); sensoraddObserver(observer);// 向Observable注册Observer root = new DefaultMutableTreeNode(Images); tree = new JTree(root); node = new DefaultMutableTreeNode(Rabbit); node = new DefaultMutableTreeNode(Devastator); rootadd(node); rootadd(node); treeaddTreeSelectionListener(new TreeSelectionListener() {// 树节点选择动作 public void valueChanged(TreeSelectionEvent e) { Object obj = egetPath()getLastPathComponent(); if (obj instanceof DefaultMutableTreeNode) { DefaultMutableTreeNode node = (DefaultMutableTreeNode)obj; if (node == root) file = ;// 选择根 if (node == node) file = rabbitjpg;// 选择node if (node == node) file = devastatorgif;// 选择node sensorsetData(file);// 改变Observable sensornotifyObservers();// 通知observer对象已改变 } } }); scroll = new JScrollPane(tree); add(scroll BorderLayoutCENTER); } public Observable getSensor() {// 返回Observable对象使Observer可以获取 return sensor; } } class Sensor extends Observable {// 定义自己的Observable private Object data; public void setData(Object newData) { data = newData; setChanged();// 改变Observable Systemoutprintln(Data changed!); } public Object getData() { return data; } } //RightPaneljava package comjunglefordtest; import javaawt*; import javaxswingJPanel; import javautilObserver; import javautilObservable; public class RightPanel extends JPanel implements Observer {// 把图片浏览视图布局在右边 private Image image; public void update(Observable subject Object obj) {// 定义接收到Observable变化后的响应动作 String file = (String)((Sensor)subject)getData(); if (!fileequals()) { image = ToolkitgetDefaultToolkit()getImage(file); MediaTracker tracker = new MediaTracker(this);// 定义图像跟蹤 trackeraddImage(image ); try { trackerwaitForID();// 等待图像的完全加载 } catch (InterruptedException e) { eprintStackTrace(); } } else image = null; repaint();// 重绘组件 } public void paintComponent(Graphic |