知識庫 / Spring RSS 訂閱

Java 觀察者模式

Spring
HongKong
4
02:06 PM · Dec 06 ,2025

1. 概述

在本教程中,我們將描述觀察者模式,並探討一些 Java 實現替代方案。

2. 觀察者模式是什麼?

觀察者模式是一種行為設計模式。它指定對象之間的通信:可觀察對象觀察者可觀察對象是一個通知觀察者其狀態發生變化的對象。

例如,一個新聞機構可以當接收到新聞時通知頻道。接收到新聞是新聞機構狀態發生變化,從而導致頻道被通知。

讓我們看看我們如何自己實現它。

首先,我們將定義NewsAgency類:

public class NewsAgency {
    private String news;
    private List<Channel> channels = new ArrayList<>();

    public void addObserver(Channel channel) {
        this.channels.add(channel);
    }

    public void removeObserver(Channel channel) {
        this.channels.remove(channel);
    }

    public void setNews(String news) {
        this.news = news;
        for (Channel channel : this.channels) {
            channel.update(this.news);
        }
    }
}

NewsAgency 是一個可觀察對象,當新聞更新時,NewsAgency 的狀態會發生改變。當改變發生時,NewsAgency 會通過調用觀察者的update()方法通知它們。

為了能夠做到這一點,可觀察對象需要保持對觀察者的引用。 在我們的例子中,這就是channels變量。

現在讓我們看看觀察者,Channel類,可能是什麼樣的。它應該具有update()方法,該方法在NewsAgency的狀態發生改變時被調用:

public class NewsChannel implements Channel {
    private String news;

    @Override
    public void update(Object news) {
        this.setNews((String) news);
    } 

    // standard getter and setter
}

通道接口僅有一個方法:

public interface Channel {
    public void update(Object o);
}

現在,如果我們將 NewsChannel 實例添加到觀察者列表中,並更改 NewsAgency 的狀態,則 NewsChannel 實例將被更新:

NewsAgency observable = new NewsAgency();
NewsChannel observer = new NewsChannel();

observable.addObserver(observer);
observable.setNews("news");
assertEquals(observer.getNews(), "news");

在 Java 核心庫中,存在一個預定義的 Observer 接口,這使得實現觀察者模式更加簡單。我們來了解一下它。

3. 使用 Observer 模式實現

java.util.Observer 接口定義了 update() 方法,因此我們無需像前一節中那樣自行定義它。

下面我們來看如何在我們的實現中使用它:

public class ONewsChannel implements Observer {

    private String news;

    @Override
    public void update(Observable o, Object news) {
        this.setNews((String) news);
    }

    // standard getter and setter
}

在這裏,第二個參數來自 Observable,正如我們稍後將看到的。

要定義 Observable,我們需要擴展 Java 的 Observable 類:

public class ONewsAgency extends Observable {
    private String news;

    public void setNews(String news) {
        this.news = news;
        setChanged();
        notifyObservers(news);
    }
}

請注意,我們不需要直接調用觀察者的 update() 方法。我們只需要調用 setChanged()notifyObservers(),Observable 類會自動完成其餘工作。

它還包含一個觀察者列表,並暴露了維護該列表的方法,addObserver()deleteObserver()。

要測試結果,只需將觀察者添加到此列表中並設置新聞即可。

ONewsAgency observable = new ONewsAgency();
ONewsChannel observer = new ONewsChannel();

observable.addObserver(observer);
observable.setNews("news");
assertEquals(observer.getNews(), "news");

觀察者接口並非完美,自Java 9版本開始已棄用。 它的一個缺點是Observable不是一個接口,而是一個類,因此子類不能作為可觀察對象使用。

此外,開發者可以覆蓋Observable中的一些同步方法,從而破壞其線程安全性。

現在讓我們來看一下 接口,它比Observer接口更推薦。

4. 使用 PropertyChangeListener 實現

在實現中,一個可觀察對象必須保持對 PropertyChangeSupport 實例的引用。這有助於在類中的屬性發生更改時向觀察者發送通知。

讓我們定義可觀察對象:

public class PCLNewsAgency {
    private String news;

    private PropertyChangeSupport support;

    public PCLNewsAgency() {
        support = new PropertyChangeSupport(this);
    }

    public void addPropertyChangeListener(PropertyChangeListener pcl) {
        support.addPropertyChangeListener(pcl);
    }

    public void removePropertyChangeListener(PropertyChangeListener pcl) {
        support.removePropertyChangeListener(pcl);
    }

    public void setNews(String value) {
        support.firePropertyChange("news", this.news, value);
        this.news = value;
    }
}

使用這項支持,我們可以添加和刪除觀察者,並在可觀測對象的狀態發生變化時通知他們:

support.firePropertyChange("news", this.news, value);

在這裏,第一個參數是觀察到的屬性的名稱。第二個和第三個參數分別是它的舊值和新值。

觀察者應實現 PropertyChangeListener

public class PCLNewsChannel implements PropertyChangeListener {

    private String news;

    public void propertyChange(PropertyChangeEvent evt) {
        this.setNews((String) evt.getNewValue());
    }

    // standard getter and setter
}

由於 PropertyChangeSupport 類負責處理我們的連接,因此我們可以從事件中恢復新的屬性值。

讓我們測試實現,以確保它也能正常工作:

PCLNewsAgency observable = new PCLNewsAgency();
PCLNewsChannel observer = new PCLNewsChannel();

observable.addPropertyChangeListener(observer);
observable.setNews("news");

assertEquals(observer.getNews(), "news");

5. 失效監聽器問題 (Lapsed Listener Problem)

所有上述實現都需要顯式註銷監聽器以避免失效監聽器問題。遺漏註銷操作可能會導致內存泄漏。

解決此問題的最佳方法是使用弱引用。當監聽器超出作用域時,它將自動從監聽器列表中移除。

6. 結論

在本文中,我們研究了兩種在 Java 中實現 觀察者模式 的方法。我們瞭解到,PropertyChangeListener 方式更具優勢。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.