分享
 
 
 

利用Observer模式解决组件间通信问题

王朝java/jsp·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

1. 问题的提出

以前做一个界面的时候常常会遇到这样的尴尬情况:希望保留各个独立的组件(类),但又希望它们之间能够相互通信。譬如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模式来解决这个问题。

2. Observer模式

设计模式分为创建型、结构型和行为型,其中行为型模式专门处理对象间通信,指定交互方式等,Observer模式就是属于行为型的一种设计模式。按照“四人帮”(Gang of Four)在“Design Patterns”里的定义,Observer模式“定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新”,这个描述正好符合我们对“组件通信”问题的需求。让我们先看看Observer模式的结构:

其中各元素的含义如下:

Subject:被观察的目标的抽象接口,它提供对观察者(Observer)的注册、注销服务,Notify方法通知Observer目标发生改变;

Object:观察者的抽象接口,Update方法是当得到Subject状态变化的通知后所要采取的动作;

ConcreteSubject:Subject的具体实现;

ConcreteObserver:Observer的具体实现 Observer模式在实现MVC结构时非常有用,为数据和数据表示解耦合。

3. Java中的Observer模式:Observer和Observable

在大致了解了Observer模式的描述之后,现在我们更为关心的是它在Java中是如何应用的。幸运的是,自从JDK 1.0起,就有了专门处理这种应用的API,这就是Observer接口和Observable类,它们是属于java.util包的一部分。看来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状态发生变化 4. Observer模式在Java GUI事件模型中应用

其实在AWT/Swing事件模型中用到了好几种设计模式,以前的JDK 1.0 AWT使用的是“基于继承的事件模型”,在该模型Component类中定义了一系列事件处理方法,如:handleEventmouseDownmouseUp等等,我们对事件的响应是通过对组件类继承并覆盖相应的事件处理方法的手段来实现,这种模型有很多缺点,事件的处理不应当由事件产生者负责,而且根据“设计模式”一书中的原则,“继承”通常被认为是“对封装性的破坏”,父子类之间的紧密耦合关系降低了灵活性,同时继承容易导致家族树规模的庞大,这些都不利于组件可重用。

JDK 1.1以后新的事件模型是被成为“基于授权的事件模型”,也就是我们现在所熟悉的Listener模型,事件的处理不再由产生事件的对象负责,而由Listener负责。尤其在Swing组件中设计MVC结构时用到了Observer模式,众所周知,MVC表示“模型-视图-控制器”,即“数据-表示逻辑-操作”,其中数据可以对应多种表示,这样视图就处在了observer的地位,而model则是subject。

5. 简单的例子

回到本文一开始的那个Explorer的例子,我们考虑做一个简单的图片浏览器,使树型选择组件和图片浏览面板在两个不同的类中,其中图片浏览面板根据所选择的树的节点显示相应的图片,所以图片浏览面板是一个observer,树是subject。由于Java单根继承的原因,我们不能同时继承JPanel和Observable,但可以用对象的组合把一个subject放到我们的类当中,并通过TreeSelectionListener触发subject的setChanged方法,并通过notifyObservers方法通知observer。

例子代码如下:

//LeftPanel.java

package com.jungleford.test;

import java.awt.BorderLayout;

import javax.swing.*;

import javax.swing.event.TreeSelectionListener;

import javax.swing.event.TreeSelectionEvent;

import javax.swing.tree.DefaultMutableTreeNode;

import java.util.Observable;

import java.util.Observer;

public final class LeftPanel extends JPanel

{// 把树型选择视图布局在左边

private JTree tree;// 树型选择视图

private JScrollPane scroll;// 让视图可滚动

private DefaultMutableTreeNode root, node1, node2;// 根节点及两个叶子

private Sensor sensor;// sensor是一个Observable,由于只能单根继承,所以作为组合成员

private String file;// 图片文件名,与RightPanel通信的内容

public LeftPanel(Observer observer)

{

file = "";

sensor = new Sensor();

sensor.addObserver(observer);// 向Observable注册Observer

root = new DefaultMutableTreeNode("Images");

tree = new JTree(root);

node1 = new DefaultMutableTreeNode("Rabbit");

node2 = new DefaultMutableTreeNode("Devastator");

root.add(node1);

root.add(node2);

tree.addTreeSelectionListener(new TreeSelectionListener()

{// 树节点选择动作

public void valueChanged(TreeSelectionEvent e)

{

Object obj = e.getPath().getLastPathComponent();

if (obj instanceof DefaultMutableTreeNode)

{

DefaultMutableTreeNode node = (DefaultMutableTreeNode)obj;

if (node == root)

file = "";// 选择根

if (node == node1)

file = "rabbit.jpg";// 选择node1

if (node == node2)

file = "devastator.gif";// 选择node2

sensor.setData(file);// 改变Observable

sensor.notifyObservers();// 通知observer,对象已改变

}

}

});

scroll = new JScrollPane(tree);

add(scroll, BorderLayout.CENTER);

}

public Observable getSensor()

{// 返回Observable对象,使Observer可以获取

return sensor;

}

}

class Sensor extends Observable

{// 定义自己的Observable

private Object data;

public void setData(Object newData)

{

data = newData;

setChanged();// 改变Observable

System.out.println("Data changed!");

}

public Object getData()

{

return data;

}

}

//RightPanel.java

package com.jungleford.test;

import java.awt.*;

import javax.swing.JPanel;

import java.util.Observer;

import java.util.Observable;

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 (!file.equals(""))

{

image = Toolkit.getDefaultToolkit().getImage(file);

MediaTracker tracker = new MediaTracker(this);// 定义图像跟踪

tracker.addImage(image, 0);

try

{

tracker.waitForID(0);// 等待图像的完全加载

}

catch (InterruptedException e)

{

e.printStackTrace();

}

}

else

image = null;

repaint();// 重绘组件

}

public void paintComponent(Graphics g)

{

g.setColor(Color.LIGHT_GRAY);

g.fillRect(0, 0, getWidth() - 1, getHeight() - 1);// 先将组件上的画面清除

if (image != null)

g.drawImage(image, 0, 0, this);// 绘制新的图像

}

}

//MainFrame.java

package com.jungleford.test;

import java.awt.*;

import javax.swing.JFrame;

public class MainFrame extends JFrame

{// 演示窗口

public static void main(String[] args)

{

MainFrame frame = new MainFrame();

RightPanel right = new RightPanel();

LeftPanel left = new LeftPanel(right);// 注册Observer

frame.getContentPane().add(left, BorderLayout.WEST);

frame.getContentPane().add(right, BorderLayout.CENTER);

frame.setTitle("Observer Test");

frame.setSize(400, 300);

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

frame.setVisible(true);

}

}

程序运行截图如下:

启动界面

点击Rabbit显示的图像

点击Devestator显示的图像

附录:Observer模式概览

摘自 设计模式

意图

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

动机

将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的移植性。我们不希望为了维持一致性而使各类紧密耦合,因为这样将降低它们的可重用性。

适用性

当一个抽象模型有两个方面,其中一个依赖于另一个,将这二者封装在独立的对象中使它们可以各自独立地改变和复用

当对一个对象的改变需要同时改变其它对象,但不知道具体有多少对象有待改变

当一个对象必须通知其它对象,但它又不能假定其它对象是什么,亦即不希望这些对象是紧密耦合的

结构图

参与者

Subject(目标)

Observer(观察者)

ConcreteSubject(具体目标)

ConcreteObserver(具体观察者)

协作图

效果

允许你独立的改变目标和观察者。你可以单独复用目标对象而无需同时复用其观察者,反之亦然。你也可以在不改动目标和其它观察者的前提下增加观察者

应用

MVC模式

相关模式

Mediator模式

Singleton模式

参考资料:

Design Patterns: Elements of Reusable Object-Oriented Software, by E. Gamma, R. Helm, R. Johnson, J. Vlissides

IBM developerWorks教程:Java设计模式101

Graphic Java 2, Mastering the JFC Volumn I: AWT, by David M. Geary

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有