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),它會對被監聽的對象的某些變化感興趣。這個接口就一個方法:
java 代碼
- public void propertyChange(PropertyChangeEvent evt) {
- // TODO Auto-generated method stub
- }
接口定義很簡單,作用也很明顯。接受一個event(被監聽者產生的PropertyChangeEvent),然后根據這個event做點反應。
- java.beans.PropertyChangeSupport
這個類用在被觀察者的類里,用來保存注冊的觀察者,并負責向他們提供被觀察者的變化信息。這個類的方法也不多,不過還是只介紹100%用到的,要不腦子就不夠使了,呵呵。
java 代碼
- public PropertyChangeSupport(Object sourceBean)
這是構造函數,參數就是被監聽者。PropertyChangeListener一般作為被監聽者的一個屬性。一般如下使用:
java 代碼
- private PropertyChangeSupport listeners = new PropertyChangeSupport(this);
注意,這個listeners可不是只代表一個監聽者,他可能是一群監聽者。那么如何這些listeners是誰呢?這回用到下面的方法了。
java 代碼
- public void addPropertyChangeListener(PropertyChangeListener listener)
這個類太容易了,把監聽者加進來。就像開十七大一樣,記者想要采訪,就得先登記一下。顯然這個方法可以多次調用(add嘛)。有加就有減:
java 代碼
- public void removePropertyChangeListener(PropertyChangeListener listener)
如果這個監聽者對被監聽者的任何變化多不感興趣了,就被被監聽者趕了出去。
好了,記者都到齊了,被監聽者有變化了就該通知人家了,使用如下方法的一個:
java 代碼
- 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
這個類我也懶得介紹,看看他的主要方法就明白怎么回事了
java 代碼
- public String getPropertyName()
- public Object getNewValue()
- public Object getOldValue()
就者三個類,再有就是具體問題具體分析了。來個例子吧,首先是被觀察者:
java 代碼
- 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的三個屬性感興趣。下面就是這些人中的一個:
java 代碼
- public class SimpleObserver implements PropertyChangeListener {
-
- ....
-
- @Override
- public void propertyChange(PropertyChangeEvent evt) {
- if(evt.getPropertyName().equals("Domain.name")){
- //do some work
- }
- }
-
- }
下面是個簡單的測試類:
java 代碼
- public class SimpleTest{
- public static void main(String[] args) {
- SimpleObserver observer = new SimpleObserver();
- Domain domain = new Domain();
- domain.addPropertyChangeListener(observer);
- domain.setName("yangsq");
- ......
- }
- }
關于JavaBean的PropertyChangeListener
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方法的執行。