操作Observer模式实现组件间通信
副标题#e#
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模式来办理这个问题。
#p#副标题#e#
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类中界说了一系列事件处理惩罚要领,如:handleEvent,mouseDown,mouseUp等等,我们对事件的响应是通过对组件类担任并包围相应的事件处理惩罚要领的手段来实现,这种模子有许多缺点,事件的处理惩罚不应当由事件发生者认真,并且按照“设计模式”一书中的原则,“担任”凡是被认为是“对封装性的粉碎”,父子类之间的细密耦合干系低落了机动性,同时担任容易导致家属树局限的复杂,这些都倒霉于组件可重用。
#p#分页标题#e#
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显示的图像