JavaBean PropertyChange 之设计模式Observer(转)
java语言里包含了许多对设计模式的直接支持,如command模式,agent模式,observer模式等。虽然java提供的对这些模式的支持很简单,不能满足比较复杂的应用。但在简单的场景下,使用这些类往往能够得到立杆见影的效果。所以,如果没有什么特殊需求,还是最好利用java的这些类。Observer模式,又称监听模式,观察者模式,是经典设计模式之一(one of GOF)。java语言中,对这种模式支持的类和接口主要有以下几个,全部来自java.beans包:
java.beans.PropertyChangeListener (interface)
java.beans.PropertyChangeSupport (class)
java.beans.PropertyChangeEvent (class)
- java.beans.PropertyChangeListener
这是一个接口,很显然,所有实现这个接口的类就是listener啦(或者叫observer),它会对被监听的对象的某些变化感兴趣。这个接口就一个方法:
- public void propertyChange(PropertyChangeEvent evt) {
- // TODO Auto-generated method stub
- }
接口定义很简单,作用也很明显。接受一个event(被监听者产生的PropertyChangeEvent),然后根据这个event做点反应。
- java.beans.PropertyChangeSupport
这个类用在被观察者的类里,用来保存注册的观察者,并负责向他们提供被观察者的变化信息。这个类的方法也不多,不过还是只介绍100%用到的,要不脑子就不够使了,呵呵。
- public PropertyChangeSupport(Object sourceBean)
这是构造函数,参数就是被监听者。PropertyChangeListener一般作为被监听者的一个属性。一般如下使用:
- private PropertyChangeSupport listeners = new PropertyChangeSupport(this);
注意,这个listeners可不是只代表一个监听者,他可能是一群监听者。那么如何这些listeners是谁呢?这回用到下面的方法了。
- public void addPropertyChangeListener(PropertyChangeListener listener)
这个类太容易了,把监听者加进来。就像开十七大一样,记者想要采访,就得先登记一下。显然这个方法可以多次调用(add嘛)。有加就有减:
- public void removePropertyChangeListener(PropertyChangeListener listener)
如果这个监听者对被监听者的任何变化多不感兴趣了,就被被监听者赶了出去。
好了,记者都到齐了,被监听者有变化了就该通知人家了,使用如下方法的一个:
- public void firePropertyChange(PropertyChangeEvent evt)
- public void firePropertyChange(String propertyName,
- boolean oldValue,
- boolean newValue)
- public void firePropertyChange(String propertyName,
- int oldValue,
- int newValue)
- public void firePropertyChange(String propertyName,
- Object oldValue,
- Object newValue)
实际上,后三个方法的参数都会封装成PropertyChangeEvent,然后调用第一个方法。不过在实际中,我们还是喜欢直接调用后三个中的一个,封装的事我们就不管了。后三个方法的参数都是三个,其中的oldValue和 newValue就是改变前后的值,第一个就是给改变一个名字,好让监听者们根据这个名子来做响应。就像开会,政府的所有信息都会被记者听到,但是有的记者只对台湾问题感兴趣,而有的记者对中日问题感兴趣。
对PropertyChangeSupport方法的介绍就这么多吧。注意,PropertyChangeSupport既然被用到了被观察者的类(一般是一个model)里,那么他的这些方法就只在被观察这里调用。
- java.beans.PropertyChangeEvent
这个类我也懒得介绍,看看他的主要方法就明白怎么回事了
- public String getPropertyName()
- public Object getNewValue()
- public Object getOldValue()
就者三个类,再有就是具体问题具体分析了。来个例子吧,首先是被观察者:
- public class Domain{
- protected String id;
- protected String name;
- protected String desName;
- protected PropertyChangeSupport listeners = new PropertyChangeSupport(this);
- public String getId() {
- return id;
- }
- public void setId(String id) {
- this.id = id;
- firePropertyChange("Domain.id", null, id);
- }
- public String getDesName() {
- return desName;
- }
- public void setDesName(String desName) {
- this.desName = desName;
- firePropertyChange("Domain.desName", null, desName);
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- firePropertyChange("Domain.name", null, name);
- }
- public void addPropertyChangeListener(PropertyChangeListener listener) {
- listeners.addPropertyChangeListener(listener);
- }
- public void firePropertyChange(String propName, Object oldValue, Object newValue) {
- listeners.firePropertyChange(propName, oldValue, newValue);
- }
- public void removePropertyChangeListener(PropertyChangeListener listener) {
- listeners.removePropertyChangeListener(listener);
- }
- }
有人对Domain的三个属性感兴趣。下面就是这些人中的一个:
- public class SimpleObserver implements PropertyChangeListener {
- ....
- @Override
- public void propertyChange(PropertyChangeEvent evt) {
- if(evt.getPropertyName().equals("Domain.name")){
- //do some work
- }
- }
- }
下面是个简单的测试类:
- public class SimpleTest{
- public static void main(String[] args) {
- SimpleObserver observer = new SimpleObserver();
- Domain domain = new Domain();
- domain.addPropertyChangeListener(observer);
- domain.setName("yangsq");
- ......
- }
- }
JavaBean的属性与一般Java程序中所指的属性,或者说与所有面向对象的程序设计语言中对象的属性是一个概念,在程序中的具体体现就是类中的变量。在JavaBean的设计中,按照属性的不同作用又细分为四类:单值属性;索引属性;关联属性;限制属性。
本文主要介绍如何使用PropertyChangeSupport类来支持关联属性事件的触发。
1.关联属性
关联属性,也称之为绑定属性。绑定属性会在属性值发生变化时,通知所有相关的监听器。为了实现一个绑定属性,必须实现两个机制。
1) 无论何时,只要属性的值发生变化,该bean必须发送一个PropertyChange事件给所有已注册的监听器。该变化可能发生在调用set方法时,或者程序的用户做出某种动作时。
2) 为了使感兴趣的监听器能够进行注册,bean必须实现以下两个方法:
void addPropertyChangeListener(PropertyChangeListener listener);
void removePropertyChangeListener(PropertyChangeListener listener);
2.使用PropertyChangeSupport 管理 监听器
可以通过java.bean包下的PropertyChangeSupport类来管理监听器。要使用这个类,bean必须有一个此类的数据域。
private PropertyChangeSupport changes = new PropertyChangeSupport(this);
这样就可以将添加和移除监听器的任务交给这个对象。
public void addPropertyChangeListener(PropertyChangeListener listener) {
changes.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
changes.removePropertyChangeListener(listener);
}
当bean的属性发生变化时,使用PropertyChangeSupport对象的firePropertyChange方法,它会将一个事件发送给所有已经注册的监听器。该方法有三个参数:属性的名字、旧的值以及新的值。属性的值必须是对象,如果是简单数据类型,则必须进行包装。
changes.firePropertyChange("ourString", oldString, newString);
所有注册的监听器实现PropertyChangeListener接口,该接口中只有一个方法。
public void propertyChange(PropertyChangeEvent e);
当bean的属性值发生变化时,该方法中的代码就会被触发。可以通过
e.getOldValue();
e.getNewValue();
来得到changes.firePropertyChange("ourString", oldString, newString);中的oldString和newString。
3.为什么要使用PropertyChangeSupport
使用这个类 管理 监听器的好处是,它是线程安全的。如果使用一个循环体来set Bean的属性,则这个类可以保证所有监听器执行触发事件的有序。
还有一个好处是,这个类支持fire带索引的属性改变事件(详见java.bean.IndexedPropertyChangeEvent)。此时向注册的监听器发送一个PropertyChangeEvent的方法为:
void fireIndexedPropertyChange(String PropertyName,int index,Object oldValue,Object newValue);
4.示例
MyBoundBean类(具体代码见附件)是一个JavaBean,我们关注它的唯一一个属性ourString的变化情况,它的初始值是Hello。并通过PropertyChange类来管理监听器。注意在set方法中会调用firePropertyChange方法。
MyBoundBean.java
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
public class MyBoundBean {
String ourString = "Hello";
private PropertyChangeSupport changes = new PropertyChangeSupport(this);
public void setString(String newString) {
String oldString = ourString;
ourString = newString;
changes.firePropertyChange("ourString", oldString, newString);
}
public String getString() {
return ourString;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
changes.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
changes.removePropertyChangeListener(listener);
}
}
MyBoundBean b = new MyBoundBean();
…
public void actionPerformed(ActionEvent e) {
jButton1.setText(textBox.getText());
b.setString(textBox.getText());
}
目标bean的属性一改变(注意,初始值是"Hello"),将会触发propertyChange方法的执行。将文本框的内容设置为目标bean的ourString属性的旧值,同时,将jButton2的test设置成目标bean的ourString属性的新值。
public void propertyChange(PropertyChangeEvent e) {
if (e.getSource() == b) {
textBox.setText(e.getOldValue().toString());
jButton2.setText(e.getNewValue().toString());
}
}
如果不实现PropertyChangeListener接口的话,可以使用匿名内部类来达到同样的效果。(具体代码见附件MyCallBound2.java)
MyBoundBean b = new MyBoundBean();
…
b.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
// 这样一来,我们就可以用自己定义的名字实现事件
ourString_propertyChange(e);
}
});
这样一来,我们就可以用自己定义的名字实现事件。
void ourString_propertyChange(PropertyChangeEvent e) {
if (e.getSource() == b) {
textBox.setText(e.getOldValue().toString());
jButton2.setText(e.getNewValue().toString());
}
}
很显然,可以观察到SimpleObserver中propertyChange方法的执行。