超碰91资源站-超碰97豆花-超碰97人妻-超碰97人人干-超碰97人人香蕉-超碰97天天操-超碰97在线资源站-超碰97资源站共享-超碰97资源站总站-超碰aa在线91-超碰av操-超碰爱爱

半岛外围网上直营

什么是觀察者模式?如何在Java 8中實(shí)現(xiàn)觀察者模式?

轉(zhuǎn)帖|使用教程|編輯:我只采一朵|2016-03-01 11:40:38.000|閱讀 605 次

概述:本文主要介紹通過使用 Java8 架構(gòu)實(shí)現(xiàn)觀察者模式,并在此基礎(chǔ)上進(jìn)一步探討關(guān)于經(jīng)典模式的復(fù)雜問題,包括匿名內(nèi)部類、lambda 表達(dá)式、線程安全以及非平凡耗時(shí)長(zhǎng)的觀察者實(shí)現(xiàn)。本文內(nèi)容雖然并不全面,很多這種模式所涉及的復(fù)雜問題,遠(yuǎn)不是一篇文章就能說清的。但是讀完本文,讀者能 了解什么是觀察者模式,它在Java中的通用性以及如何處理在 Java 中實(shí)現(xiàn)觀察者模式時(shí)的一些常見問題。

# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>

察者(Observer)模式又名發(fā)布-訂閱(Publish/Subscribe)模式,是四人組(GoF,即 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides)在1994合著的《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》中提出的(詳見書中293-313頁(yè))。盡管這種模式已經(jīng)有相當(dāng)長(zhǎng)的歷史, 它仍然廣泛適用于各種場(chǎng)景,甚至成為了標(biāo)準(zhǔn)Java庫(kù)的一個(gè)組成部分。目前雖然已經(jīng)有大量關(guān)于觀察者模式的文章,但它們都專注于在 Java 中的實(shí)現(xiàn),卻忽視了開發(fā)者在Java中使用觀察者模式時(shí)遇到的各種問題。

本文的寫作初衷就是為了填補(bǔ)這一空白:本文主要介紹通過使用 Java8 架構(gòu)實(shí)現(xiàn)觀察者模式,并在此基礎(chǔ)上進(jìn)一步探討關(guān)于經(jīng)典模式的復(fù)雜問題,包括匿名內(nèi)部類、lambda 表達(dá)式、線程安全以及非平凡耗時(shí)長(zhǎng)的觀察者實(shí)現(xiàn)。本文內(nèi)容雖然并不全面,很多這種模式所涉及的復(fù)雜問題,遠(yuǎn)不是一篇文章就能說清的。但是讀完本文,讀者能 了解什么是觀察者模式,它在Java中的通用性以及如何處理在 Java 中實(shí)現(xiàn)觀察者模式時(shí)的一些常見問題。

觀察者模式

根據(jù) GoF 提出的經(jīng)典定義,觀察者模式的主旨是:

定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新。

什么意思呢?很多軟件應(yīng)用中,對(duì)象之間的狀態(tài)都是互相依賴的。例如,如果一個(gè)應(yīng)用專注于數(shù)值數(shù)據(jù)加工,這個(gè)數(shù)據(jù)也許會(huì)通過圖形用戶界面(GUI)的表格或圖 表來(lái)展現(xiàn)或者兩者同時(shí)使用,也就是說,當(dāng)?shù)讓訑?shù)據(jù)更新時(shí),相應(yīng)的 GUI 組件也要更新。問題的關(guān)鍵在于如何做到底層數(shù)據(jù)更新時(shí) GUI 組件也隨之更新,同時(shí)盡量減小 GUI 組件和底層數(shù)據(jù)的耦合度。

一種簡(jiǎn)單且不可擴(kuò)展的解決方案是給管理這些底層數(shù)據(jù)的對(duì)象該表格和圖像 GUI 組件的引用,使得對(duì)象可以在底層數(shù)據(jù)變化時(shí)能夠通知 GUI 組件。顯然,對(duì)于處理有更多 GUI 組件的復(fù)雜應(yīng)用,這個(gè)簡(jiǎn)單的解決方案很快顯示出其不足。例如,有20個(gè) GUI 組件都依賴于底層數(shù)據(jù),那么管理底層數(shù)據(jù)的對(duì)象就需要維護(hù)指向這20個(gè)組件的引用。隨著依賴于相關(guān)數(shù)據(jù)的對(duì)象數(shù)量的增加,數(shù)據(jù)管理和對(duì)象之間的耦合度也變 得難以控制。

另一個(gè)更好的解決方案是允許對(duì)象注冊(cè)獲取感興趣數(shù)據(jù)更新的權(quán)限,當(dāng)數(shù)據(jù)變化時(shí),數(shù)據(jù)管理器就會(huì)通知這些對(duì)象。通俗地說就是,讓 感興趣的數(shù)據(jù)對(duì)象告訴管理器:“當(dāng)數(shù)據(jù)變化時(shí)請(qǐng)通知我”。此外,這些對(duì)象不僅可以注冊(cè)獲取更新通知,也可以取消注冊(cè),保證數(shù)據(jù)管理器在數(shù)據(jù)變化時(shí)不再通知 該對(duì)象。在 GoF 的原始定義中,注冊(cè)獲取更新的對(duì)象叫作“觀察者”(observer),對(duì)應(yīng)的數(shù)據(jù)管理器叫作“目標(biāo)”(Subject),觀察者感興趣的數(shù)據(jù)叫作“目標(biāo) 狀態(tài)”,注冊(cè)過程叫“添加”(attach),撤銷觀察的過程叫“移除”(detach)。前文已經(jīng)提到觀察者模式又叫發(fā)布-訂閱模式,可以理解為客戶訂 閱關(guān)于目標(biāo)的觀察者,當(dāng)目標(biāo)狀態(tài)更新時(shí),目標(biāo)把這些更新發(fā)布給訂閱者(這種設(shè)計(jì)模式擴(kuò)展為通用架構(gòu),稱為發(fā)布——訂閱架構(gòu))。這些概念可以用下面的類圖表示:

Java8觀察者模式

具體觀察者(ConcereteObserver)用來(lái)接收更新的狀態(tài)變化,同時(shí)將指向具體主題(ConcereteSubject)的引用傳遞給它的構(gòu) 造函數(shù)。這為具體觀察者提供了指向具體主題的引用,在狀態(tài)變化時(shí)可由此獲得更新。簡(jiǎn)單來(lái)說,具體觀察者會(huì)被告知主題更新,同時(shí)用其構(gòu)造函數(shù)中的引用來(lái)獲取 具體主題的狀態(tài),最后將這些檢索狀態(tài)對(duì)象存儲(chǔ)在具體觀察者的觀察狀態(tài)(observerState)屬性下。這一過程如下面的序列圖所示:

Java8觀察者模式

經(jīng)典模式的專業(yè)化

盡管觀察者模式是通用的,但也有很多專業(yè)化的模式,最常見是以下兩種:

  1. 為State對(duì)象提供一個(gè)參數(shù),傳給觀察者調(diào)用的Update方法。在經(jīng)典模式下,當(dāng)觀察者被通知Subject狀態(tài)發(fā)生變化后,會(huì)直接從 Subject獲得其更新后狀態(tài)。這要求觀察者保存指向獲取狀態(tài)的對(duì)象引用。這樣就形成了一個(gè)循環(huán)引用,ConcreteSubject的引用指向其觀察 者列表,ConcreteObserver的引用指向能獲得主題狀態(tài)的ConcreteSubject。除了獲得更新的狀態(tài),觀察者和其注冊(cè)監(jiān)聽的 Subject間并沒有聯(lián)系,觀察者關(guān)心的是State對(duì)象,而非Subject本身。也就是說,很多情況下都將ConcreteObserver和 ConcreteSubject強(qiáng)行聯(lián)系一起,相反,當(dāng)ConcreteSubject調(diào)用Update函數(shù)時(shí),將State對(duì)象傳遞給 ConcreteObserver,二者就無(wú)需關(guān)聯(lián)。ConcreteObserver和State對(duì)象之間關(guān)聯(lián)減小了觀察者和State之間的依賴程度 (關(guān)聯(lián)和依賴的更多區(qū)別請(qǐng)參見Martin Fowler’s的文章)。

  2. 將Subject抽象類和ConcreteSubject合并到一個(gè) singleSubject類中。多數(shù)情況下,Subject使用抽象類并不會(huì)提升程序的靈活性和可擴(kuò)展性,因此,將這一抽象類和具體類合并簡(jiǎn)化了設(shè)計(jì)。

這兩個(gè)專業(yè)化的模式組合后,其簡(jiǎn)化類圖如下:

Java8觀察者模式

在這些專業(yè)化的模式中,靜態(tài)類結(jié)構(gòu)大大簡(jiǎn)化,類之間的相互作用也得以簡(jiǎn)化。此時(shí)的序列圖如下:

Java8觀察者模式

專業(yè)化模式另一特點(diǎn)是刪除了 ConcreteObserver 的成員變量 observerState。有時(shí)候具體觀察者并不需要保存Subject的最新狀態(tài),而只需要監(jiān)測(cè)狀態(tài)更新時(shí) Subject 的狀態(tài)。例如,如果觀察者將成員變量的值更新到標(biāo)準(zhǔn)輸出上,就可以刪除 observerState,這樣一來(lái)就刪除了ConcreteObserver和State類之間的關(guān)聯(lián)。

更常見的命名規(guī)則

經(jīng)典模式甚至是前文提到的專業(yè)化模式都用的是attach,detach和observer等術(shù)語(yǔ),而Java實(shí)現(xiàn)中很多都是用的不同的詞典,包括 register,unregister,listener等。值得一提的是State是listener需要監(jiān)測(cè)變化的所有對(duì)象的統(tǒng)稱,狀態(tài)對(duì)象的具體 名稱需要看觀察者模式用到的場(chǎng)景。例如,在listener監(jiān)聽事件發(fā)生場(chǎng)景下的觀察者模式,已注冊(cè)的listener將會(huì)在事件發(fā)生時(shí)收到通知,此時(shí)的 狀態(tài)對(duì)象就是event,也就是事件是否發(fā)生。

平時(shí)實(shí)際應(yīng)用中目標(biāo)的命名很少包含Subject。例如,創(chuàng)建一個(gè)關(guān)于動(dòng)物園的應(yīng)用,注冊(cè)多 個(gè)監(jiān)聽器用于觀察Zoo類,并在新動(dòng)物進(jìn)入動(dòng)物園時(shí)收到通知。該案例中的目標(biāo)是Zoo類,為了和所給問題域保持術(shù)語(yǔ)一致,將不會(huì)用到Subject這樣的 詞匯,也就是說Zoo類不會(huì)命名為ZooSubject。

監(jiān)聽器的命名一般都會(huì)跟著Listener后綴,例如前文提到的監(jiān)測(cè)新動(dòng)物加入的 監(jiān)聽器會(huì)命名為AnimalAddedListener。類似的,register,、unregister和notify等函數(shù)命名常會(huì)以其對(duì)應(yīng)的監(jiān)聽 器名作后綴,例如AnimalAddedListener的register、unregister、notify函數(shù)會(huì)被命名為 registerAnimalAddedListener、 unregisterAnimalAddedListener和notifyAnimalAddedListeners,需要注意的是notify函數(shù)名 的s,因?yàn)閚otify函數(shù)處理的是多個(gè)而非單一監(jiān)聽器。

這種命名方式會(huì)顯得冗長(zhǎng),而且通常一個(gè)subject會(huì)注冊(cè)多個(gè)類型的監(jiān)聽器,如 前面提到的動(dòng)物園的例子,Zoo內(nèi)除了注冊(cè)監(jiān)聽動(dòng)物新增的監(jiān)聽器,還需注冊(cè)監(jiān)聽動(dòng)物減少監(jiān)聽器,此時(shí)就會(huì)有兩種register函數(shù): (registerAnimalAddedListener和 registerAnimalRemovedListener,這種方式處理,監(jiān)聽器的類型作為一個(gè)限定符,表示其應(yīng)觀察者的類型。另一解決方案是創(chuàng)建一 個(gè)registerListener函數(shù)然后重載,但是方案一能更方便的知道哪個(gè)監(jiān)聽器正在監(jiān)聽,重載是比較小眾的做法。

另一慣用語(yǔ)法是用on前綴而不是update,例如update函數(shù)命名為onAnimalAdded而不是updateAnimalAdded。這種情況在監(jiān)聽器獲得一個(gè)序列的通知時(shí)更常見,如向list中新增一個(gè)動(dòng)物,但很少用于更新一個(gè)單獨(dú)的數(shù)據(jù),比如動(dòng)物的名字。

接下來(lái)本文將使用Java的符號(hào)規(guī)則,雖然符號(hào)規(guī)則不會(huì)改變系統(tǒng)的真實(shí)設(shè)計(jì)和實(shí)現(xiàn),但是使用其他開發(fā)者都熟悉的術(shù)語(yǔ)是很重要的開發(fā)準(zhǔn)則,因此要熟悉上文描述的Java中的觀察者模式符號(hào)規(guī)則。下文將在Java8環(huán)境下用一個(gè)簡(jiǎn)單例子來(lái)闡述上述概念。

一個(gè)簡(jiǎn)單的實(shí)例

還是前面提到的動(dòng)物園的例子,使用Java8的API接口實(shí)現(xiàn)一個(gè)簡(jiǎn)單的系統(tǒng),說明觀察者模式的基本原理。問題描述為:

創(chuàng)建一個(gè)系統(tǒng)zoo,允許用戶監(jiān)聽和撤銷監(jiān)聽添加新對(duì)象animal的狀態(tài),另外再創(chuàng)建一個(gè)具體監(jiān)聽器,負(fù)責(zé)輸出新增動(dòng)物的name。

根據(jù)前面對(duì)觀察者模式的學(xué)習(xí)知道實(shí)現(xiàn)這樣的應(yīng)用需要?jiǎng)?chuàng)建4個(gè)類,具體是:

  1. Zoo類:即模式中的主題,負(fù)責(zé)存儲(chǔ)動(dòng)物園中的所有動(dòng)物,并在新動(dòng)物加入時(shí)通知所有已注冊(cè)的監(jiān)聽器。

  2. Animal類:代表動(dòng)物對(duì)象。

  3. AnimalAddedListener類:即觀察者接口。

  4. PrintNameAnimalAddedListener:具體的觀察者類,負(fù)責(zé)輸出新增動(dòng)物的name。

首先我們創(chuàng)建一個(gè)Animal類,它是一個(gè)包含name成員變量、構(gòu)造函數(shù)、getter和setter方法的簡(jiǎn)單Java對(duì)象,代碼如下:

public class Animal {
    private String name;
    public Animal (String name) {
        this.name = name;
    }
    public String getName () {
        return this.name;
    }
    public void setName (String name) {
        this.name = name;
    }
}

用這個(gè)類代表動(dòng)物對(duì)象,接下來(lái)就可以創(chuàng)建AnimalAddedListener接口了:

public interface AnimalAddedListener {
    public void onAnimalAdded (Animal animal);
}

前面兩個(gè)類很簡(jiǎn)單,就不再詳細(xì)介紹,接下來(lái)創(chuàng)建Zoo類:

public class Zoo {
    private List<animal> animals = new ArrayList<>();
    private List<animaladdedlistener> listeners = new ArrayList<>();
    public void addAnimal (Animal animal) {
        // Add the animal to the list of animals
        this.animals.add(animal);
        // Notify the list of registered listeners
        this.notifyAnimalAddedListeners(animal);
    }
    public void registerAnimalAddedListener (AnimalAddedListener listener) {
        // Add the listener to the list of registered listeners
        this.listeners.add(listener);
    }
    public void unregisterAnimalAddedListener (AnimalAddedListener listener) {
        // Remove the listener from the list of the registered listeners
        this.listeners.remove(listener);
    }
    protected void notifyAnimalAddedListeners (Animal animal) {
        // Notify each of the listeners in the list of registered listeners
        this.listeners.forEach(listener -> listener.updateAnimalAdded(animal));
    }
}

這個(gè)類比前面兩個(gè)都復(fù)雜,其包含兩個(gè)list,一個(gè)用來(lái)存儲(chǔ)動(dòng)物園中所有動(dòng)物,另一個(gè)用來(lái)存儲(chǔ)所有的監(jiān)聽器,鑒于animals和listener 集合存儲(chǔ)的對(duì)象都很簡(jiǎn)單,本文選擇了ArrayList來(lái)存儲(chǔ)。存儲(chǔ)監(jiān)聽器的具體數(shù)據(jù)結(jié)構(gòu)要視問題而定,比如對(duì)于這里的動(dòng)物園問題,如果監(jiān)聽器有優(yōu)先級(jí), 那就應(yīng)該選擇其他的數(shù)據(jù)結(jié)構(gòu),或者重寫監(jiān)聽器的register算法。

注冊(cè)和移除的實(shí)現(xiàn)都是簡(jiǎn)單的委托方式:各個(gè)監(jiān)聽器作為參數(shù)從監(jiān)聽者的 監(jiān)聽列表增加或者移除。notify函數(shù)的實(shí)現(xiàn)與觀察者模式的標(biāo)準(zhǔn)格式稍微偏離,它包括輸入?yún)?shù):新增加的animal,這樣一來(lái)notify函數(shù)就可以 把新增加的animal引用傳遞給監(jiān)聽器了。用streams API的forEach函數(shù)遍歷監(jiān)聽器,對(duì)每個(gè)監(jiān)聽器執(zhí)行theonAnimalAdded函數(shù)。

在addAnimal函數(shù)中,新增的 animal對(duì)象和監(jiān)聽器各自添加到對(duì)應(yīng)list。如果不考慮通知過程的復(fù)雜性,這一邏輯應(yīng)包含在方便調(diào)用的方法中,只需要傳入指向新增animal對(duì)象 的引用即可,這就是通知監(jiān)聽器的邏輯實(shí)現(xiàn)封裝在notifyAnimalAddedListeners函數(shù)中的原因,這一點(diǎn)在addAnimal的實(shí)現(xiàn)中 也提到過。

除了notify函數(shù)的邏輯問題,需要強(qiáng)調(diào)一下對(duì)notify函數(shù)可見性的爭(zhēng)議問題。在經(jīng)典的觀察者模型中,如GoF在設(shè)計(jì)模式 一書中第301頁(yè)所說,notify函數(shù)是public型的,然而盡管在經(jīng)典模式中用到,這并不意味著必須是public的。選擇可見性應(yīng)該基于應(yīng)用,例 如本文的動(dòng)物園的例子,notify函數(shù)是protected類型,并不要求每個(gè)對(duì)象都可以發(fā)起一個(gè)注冊(cè)觀察者的通知,只需保證對(duì)象能從父類繼承該功能即 可。當(dāng)然,也并非完全如此,需要弄清楚哪些類可以激活notify函數(shù),然后再由此確定函數(shù)的可見性。

接下來(lái)需要實(shí)現(xiàn)PrintNameAnimalAddedListener類,這個(gè)類用System.out.println方法將新增動(dòng)物的name輸出,具體代碼如下:

public class PrintNameAnimalAddedListener implements AnimalAddedListener {
    @Override
    public void updateAnimalAdded (Animal animal) {
        // Print the name of the newly added animal
        System.out.println("Added a new animal with name '" + animal.getName() + "'");
    }
}

最后要實(shí)現(xiàn)驅(qū)動(dòng)應(yīng)用的主函數(shù):

public class Main {
    public static void main (String[] args) {
        // Create the zoo to store animals
        Zoo zoo = new Zoo();
        // Register a listener to be notified when an animal is added
        zoo.registerAnimalAddedListener(new PrintNameAnimalAddedListener());
        // Add an animal notify the registered listeners
        zoo.addAnimal(new Animal("Tiger"));
    }
}

主函數(shù)只是簡(jiǎn)單的創(chuàng)建了一個(gè)zoo對(duì)象,注冊(cè)了一個(gè)輸出動(dòng)物name的監(jiān)聽器,并新建了一個(gè)animal對(duì)象以觸發(fā)已注冊(cè)的監(jiān)聽器,最后的輸出為:

Added a new animal with name 'Tiger'

新增監(jiān)聽器

當(dāng)監(jiān)聽器重新建立并將其添加到Subject時(shí),觀察者模式的優(yōu)勢(shì)就充分顯示出來(lái)。例如,想添加一個(gè)計(jì)算動(dòng)物園中動(dòng)物 總數(shù)的監(jiān)聽器,只需要新建一個(gè)具體的監(jiān)聽器類并注冊(cè)到Zoo類即可,而無(wú)需對(duì)zoo類做任何修改。添加計(jì)數(shù)監(jiān)聽器 CountingAnimalAddedListener代碼如下:

public class CountingAnimalAddedListener implements AnimalAddedListener {
    private static int animalsAddedCount = 0;
    @Override
    public void updateAnimalAdded (Animal animal) {
        // Increment the number of animals
        animalsAddedCount++;
        // Print the number of animals
        System.out.println("Total animals added: " + animalsAddedCount);
    }
}

修改后的main函數(shù)如下:

public class Main {
    public static void main (String[] args) {
        // Create the zoo to store animals
        Zoo zoo = new Zoo();
        // Register listeners to be notified when an animal is added
        zoo.registerAnimalAddedListener(new PrintNameAnimalAddedListener());
        zoo.registerAnimalAddedListener(new CountingAnimalAddedListener());
        // Add an animal notify the registered listeners
        zoo.addAnimal(new Animal("Tiger"));
        zoo.addAnimal(new Animal("Lion"));
        zoo.addAnimal(new Animal("Bear"));
    }
}

輸出結(jié)果為:

Added a new animal with name 'Tiger'
Total animals added: 1
Added a new animal with name 'Lion'
Total animals added: 2
Added a new animal with name 'Bear'
Total animals added: 3

使用者可在僅修改監(jiān)聽器注冊(cè)代碼的情況下,創(chuàng)建任意監(jiān)聽器。具有此可擴(kuò)展性主要是因?yàn)镾ubject和觀察者接口關(guān)聯(lián),而不是直接和ConcreteObserver關(guān)聯(lián)。只要接口不被修改,調(diào)用接口的Subject就無(wú)需修改。

匿名內(nèi)部類,Lambda函數(shù)和監(jiān)聽器注冊(cè)

Java8 的一大改進(jìn)是增加了功能特性,如增加了lambda函數(shù)。在引進(jìn)lambda函數(shù)之前,Java通過匿名內(nèi)部類提供了類似的功能,這些類在很多已有的應(yīng)用 中仍在使用。在觀察者模式下,隨時(shí)可以創(chuàng)建新的監(jiān)聽器而無(wú)需創(chuàng)建具體觀察者類,例如,PrintNameAnimalAddedListener類可以在 main函數(shù)中用匿名內(nèi)部類實(shí)現(xiàn),具體實(shí)現(xiàn)代碼如下:

public class Main {
    public static void main (String[] args) {
        // Create the zoo to store animals
        Zoo zoo = new Zoo();
        // Register listeners to be notified when an animal is added
        zoo.registerAnimalAddedListener(new AnimalAddedListener() {
            @Override
            public void updateAnimalAdded (Animal animal) {
                // Print the name of the newly added animal
                System.out.println("Added a new animal with name '" + animal.getName() + "'");
            }
        });
        // Add an animal notify the registered listeners
        zoo.addAnimal(new Animal("Tiger"));
    }
}

類似的,lambda函數(shù)也可以用以完成此類任務(wù):

public class Main {
    public static void main (String[] args) {
        // Create the zoo to store animals
        Zoo zoo = new Zoo();
        // Register listeners to be notified when an animal is added
        zoo.registerAnimalAddedListener(
            (animal) -> System.out.println("Added a new animal with name '" + animal.getName() + "'")
        );
        // Add an animal notify the registered listeners
        zoo.addAnimal(new Animal("Tiger"));
    }
}

需要注意的是lambda函數(shù)僅適用于監(jiān)聽器接口只有一個(gè)函數(shù)的情況,這個(gè)要求雖然看起來(lái)嚴(yán)格,但實(shí)際上很多監(jiān)聽器都是單一函數(shù)的,如示例中的AnimalAddedListener。如果接口有多個(gè)函數(shù),可以選擇使用匿名內(nèi)部類。

隱 式注冊(cè)創(chuàng)建的監(jiān)聽器存在此類問題:由于對(duì)象是在注冊(cè)調(diào)用的范圍內(nèi)創(chuàng)建的,所以不可能將引用存儲(chǔ)一個(gè)到具體監(jiān)聽器。這意味著,通過lambda函數(shù)或者匿名 內(nèi)部類注冊(cè)的監(jiān)聽器不可以撤銷注冊(cè),因?yàn)槌蜂N函數(shù)需要傳入已經(jīng)注冊(cè)監(jiān)聽器的引用。解決這個(gè)問題的一個(gè)簡(jiǎn)單方法是在 registerAnimalAddedListener函數(shù)中返回注冊(cè)監(jiān)聽器的引用。如此一來(lái),就可以撤銷注冊(cè)用lambda函數(shù)或匿名內(nèi)部類創(chuàng)建的監(jiān) 聽器,改進(jìn)后的方法代碼如下:

public AnimalAddedListener registerAnimalAddedListener (AnimalAddedListener listener) {
    // Add the listener to the list of registered listeners
    this.listeners.add(listener); 
    return listener;
}

重新設(shè)計(jì)的函數(shù)交互的客戶端代碼如下:

public class Main {
    public static void main (String[] args) {
        // Create the zoo to store animals
        Zoo zoo = new Zoo();
        // Register listeners to be notified when an animal is added
        AnimalAddedListener listener = zoo.registerAnimalAddedListener(
            (animal) -> System.out.println("Added a new animal with name '" + animal.getName() + "'")
        );
        // Add an animal notify the registered listeners
        zoo.addAnimal(new Animal("Tiger"));
        // Unregister the listener
        zoo.unregisterAnimalAddedListener(listener);
        // Add another animal, which will not print the name, since the listener
        // has been previously unregistered
        zoo.addAnimal(new Animal("Lion"));
    }
}

此時(shí)的結(jié)果輸出只有Added a new animal with name ‘Tiger’,因?yàn)樵诘诙€(gè)animal加入之前監(jiān)聽器已經(jīng)撤銷了:

Added a new animal with name 'Tiger'

如果采用更復(fù)雜的解決方案,register函數(shù)也可以返回receipt類,以便unregister監(jiān)聽器調(diào)用,例如:

public class AnimalAddedListenerReceipt {
    private final AnimalAddedListener listener;
    public AnimalAddedListenerReceipt (AnimalAddedListener listener) {
        this.listener = listener;
    }
    public final AnimalAddedListener getListener () {
        return this.listener;
    }
}

receipt會(huì)作為注冊(cè)函數(shù)的返回值,以及撤銷注冊(cè)函數(shù)輸入?yún)?shù),此時(shí)的zoo實(shí)現(xiàn)如下所示:

public class ZooUsingReceipt {
    // ...Existing attributes and constructor...
    public AnimalAddedListenerReceipt registerAnimalAddedListener (AnimalAddedListener listener) {
        // Add the listener to the list of registered listeners
        this.listeners.add(listener);
        return new AnimalAddedListenerReceipt(listener);
    }
    public void unregisterAnimalAddedListener (AnimalAddedListenerReceipt receipt) {
        // Remove the listener from the list of the registered listeners
        this.listeners.remove(receipt.getListener());
    }
    // ...Existing notification method...
}

上面描述的接收實(shí)現(xiàn)機(jī)制允許保存信息供監(jiān)聽器撤銷時(shí)調(diào)用的,也就是說如果撤銷注冊(cè)算法依賴于Subject注冊(cè)監(jiān)聽器時(shí)的狀態(tài),則此狀態(tài)將被保存,如果撤銷注冊(cè)只需要指向之前注冊(cè)監(jiān)聽器的引用,這樣的話接收技術(shù)則顯得麻煩,不推薦使用。

除了特別復(fù)雜的具體監(jiān)聽器,最常見的注冊(cè)監(jiān)聽器的方法是通過lambda函數(shù)或通過匿名內(nèi)部類注冊(cè)。當(dāng)然,也有例外,那就是包含subject實(shí)現(xiàn)觀察者接口的類和注冊(cè)一個(gè)包含調(diào)用該引用目標(biāo)的監(jiān)聽器。如下面代碼所示的案例:

public class ZooContainer implements AnimalAddedListener {
    private Zoo zoo = new Zoo();
    public ZooContainer () {
        // Register this object as a listener
        this.zoo.registerAnimalAddedListener(this);
    }
    public Zoo getZoo () {
        return this.zoo;
    }
    @Override
        public void updateAnimalAdded (Animal animal) {
        System.out.println("Added animal with name '" + animal.getName() + "'");
    }
    public static void main (String[] args) {
        // Create the zoo container
        ZooContainer zooContainer = new ZooContainer();
        // Add an animal notify the innerally notified listener
        zooContainer.getZoo().addAnimal(new Animal("Tiger"));
    }
}

這種方法只適用于簡(jiǎn)單情況而且代碼看起來(lái)不夠?qū)I(yè),盡管如此,它還是深受現(xiàn)代Java開發(fā)人員的喜愛,因此了解這個(gè)例子的工作原理很有必要。因?yàn)?ZooContainer實(shí)現(xiàn)了AnimalAddedListener接口,那么ZooContainer的實(shí)例(或者說對(duì)象)就可以注冊(cè)為 AnimalAddedListener。ZooContainer類中,該引用代表當(dāng)前對(duì)象即ZooContainer的一個(gè)實(shí)例,所以可以被用作 AnimalAddedListener。

通常,不是要求所有的container類都實(shí)現(xiàn)此類功能,而且實(shí)現(xiàn)監(jiān)聽器接口的container類只能調(diào)用Subject的注冊(cè)函數(shù),只是簡(jiǎn)單把該引用作為監(jiān)聽器的對(duì)象傳給register函數(shù)。在接下來(lái)的章節(jié)中,將介紹多線程環(huán)境的常見問題和解決方案。

線程安全的實(shí)現(xiàn)

前 面章節(jié)介紹了在現(xiàn)代Java環(huán)境下的實(shí)現(xiàn)觀察者模式,雖然簡(jiǎn)單但很完整,但這一實(shí)現(xiàn)忽略了一個(gè)關(guān)鍵性問題:線程安全。大多數(shù)開放的Java應(yīng)用都是多線程 的,而且觀察者模式也多用于多線程或異步系統(tǒng)。例如,如果外部服務(wù)更新其數(shù)據(jù)庫(kù),那么應(yīng)用也會(huì)異步地收到消息,然后用觀察者模式通知內(nèi)部組件更新,而不是 內(nèi)部組件直接注冊(cè)監(jiān)聽外部服務(wù)。

觀察者模式的線程安全主要集中在模式的主體上,因?yàn)樾薷淖?cè)監(jiān)聽器集合時(shí)很可能發(fā)生線程沖突,比如,一個(gè)線 程試圖添加一個(gè)新的監(jiān)聽器,而另一線程又試圖添加一個(gè)新的animal對(duì)象,這將觸發(fā)對(duì)所有注冊(cè)監(jiān)聽器的通知。鑒于先后順序,在已注冊(cè)的監(jiān)聽器收到新增動(dòng) 物的通知前,第一個(gè)線程可能已經(jīng)完成也可能尚未完成新監(jiān)聽器的注冊(cè)。這是一個(gè)經(jīng)典的線程資源競(jìng)爭(zhēng)案例,正是這一現(xiàn)象告訴開發(fā)者們需要一個(gè)機(jī)制來(lái)保證線程安 全。

這一問題的最簡(jiǎn)單的解決方案是:所有訪問或修改注冊(cè)監(jiān)聽器list的操作都須遵循Java的同步機(jī)制,比如:

public synchronized AnimalAddedListener registerAnimalAddedListener (AnimalAddedListener listener) { /*...*/ }
public synchronized void unregisterAnimalAddedListener (AnimalAddedListener listener) { /*...*/ }
public synchronized void notifyAnimalAddedListeners (Animal animal) { /*...*/ }

這樣一來(lái),同一時(shí)刻只有一個(gè)線程可以修改或訪問已注冊(cè)的監(jiān)聽器列表,可以成功地避免資源競(jìng)爭(zhēng)問題,但是新問題又出現(xiàn)了,這樣的約束太過嚴(yán)格 (synchronized關(guān)鍵字和Java并發(fā)模型的更多信息,請(qǐng)參閱官方網(wǎng)頁(yè))。通過方法同步,可以時(shí)刻觀測(cè)對(duì)監(jiān)聽器list的并發(fā)訪問,注冊(cè)和撤銷 監(jiān)聽器對(duì)監(jiān)聽器list而言是寫操作,而通知監(jiān)聽器訪問監(jiān)聽器list是只讀操作。由于通過通知訪問是讀操作,因此是可以多個(gè)通知操作同時(shí)進(jìn)行的。

因 此,只要沒有監(jiān)聽器注冊(cè)或撤銷注冊(cè),任意多的并發(fā)通知都可以同時(shí)執(zhí)行,而不會(huì)引發(fā)對(duì)注冊(cè)的監(jiān)聽器列表的資源爭(zhēng)奪。當(dāng)然,其他情況下的資源爭(zhēng)奪現(xiàn)象存在已 久,為了解決這一問題,設(shè)計(jì)了ReadWriteLock用以分開管理讀寫操作的資源鎖定。Zoo類的線程安全ThreadSafeZoo實(shí)現(xiàn)代碼如下:

public class ThreadSafeZoo {
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    protected final Lock readLock = readWriteLock.readLock();
    protected final Lock writeLock = readWriteLock.writeLock();
    private List<animal> animals = new ArrayList<>();
    private List<animaladdedlistener> listeners = new ArrayList<>();
    public void addAnimal (Animal animal) {
        // Add the animal to the list of animals
        this.animals.add(animal);
        // Notify the list of registered listeners
        this.notifyAnimalAddedListeners(animal);
    }
    public AnimalAddedListener registerAnimalAddedListener (AnimalAddedListener listener) {
        // Lock the list of listeners for writing
        this.writeLock.lock();
        try {
            // Add the listener to the list of registered listeners
            this.listeners.add(listener);
        }
        finally {
            // Unlock the writer lock
            this.writeLock.unlock();
        }
        return listener;
    }
    public void unregisterAnimalAddedListener (AnimalAddedListener listener) {
        // Lock the list of listeners for writing
        this.writeLock.lock();
        try {
            // Remove the listener from the list of the registered listeners
            this.listeners.remove(listener);
        }
        finally {
            // Unlock the writer lock
            this.writeLock.unlock();
        }
    }
    public void notifyAnimalAddedListeners (Animal animal) {
        // Lock the list of listeners for reading
        this.readLock.lock();
        try {
            // Notify each of the listeners in the list of registered listeners
            this.listeners.forEach(listener -> listener.updateAnimalAdded(animal));
        }
        finally {
            // Unlock the reader lock
            this.readLock.unlock();
        }
    }
}

通過這樣部署,Subject的實(shí)現(xiàn)能確保線程安全并且多個(gè)線程可以同時(shí)發(fā)布通知。但盡管如此,依舊存在兩個(gè)不容忽略的資源競(jìng)爭(zhēng)問題:

  1. 對(duì)每個(gè)監(jiān)聽器的并發(fā)訪問。多個(gè)線程可以同時(shí)通知監(jiān)聽器要新增動(dòng)物了,這意味著一個(gè)監(jiān)聽器可能會(huì)同時(shí)被多個(gè)線程同時(shí)調(diào)用。

  2. 對(duì)animal list的并發(fā)訪問。多個(gè)線程可能會(huì)同時(shí)向animal list添加對(duì)象,如果通知的先后順序存在影響,那就可能導(dǎo)致資源競(jìng)爭(zhēng),這就需要一個(gè)并發(fā)操作處理機(jī)制來(lái)避免這一問題。如果注冊(cè)的監(jiān)聽器列表在收到通知添 加animal2后,又收到通知添加animal1,此時(shí)就會(huì)產(chǎn)生資源競(jìng)爭(zhēng)。但是如果animal1和animal2的添加由不同的線程執(zhí)行,也是有可能 在animal2前完成對(duì)animal1添加操作,具體來(lái)說就是線程1在通知監(jiān)聽器前添加animal1并鎖定模塊,線程2添加animal2并通知監(jiān)聽 器,然后線程1通知監(jiān)聽器animal1已經(jīng)添加。雖然在不考慮先后順序時(shí),可以忽略資源競(jìng)爭(zhēng),但問題是真實(shí)存在的。

對(duì)監(jiān)聽器的并發(fā)訪問

并發(fā)訪問監(jiān)聽器可以通過保證監(jiān)聽器的線程安全來(lái)實(shí)現(xiàn)。秉承著類的“責(zé)任自負(fù)”精神,監(jiān)聽器有“義 務(wù)”確保自身的線程安全。例如,對(duì)于前面計(jì)數(shù)的監(jiān)聽器,多線程的遞增或遞減動(dòng)物數(shù)量可能導(dǎo)致線程安全問題,要避免這一問題,動(dòng)物數(shù)的計(jì)算必須是原子操作 (原子變量或方法同步),具體解決代碼如下:

public class ThreadSafeCountingAnimalAddedListener implements AnimalAddedListener {
    private static AtomicLong animalsAddedCount = new AtomicLong(0);
    @Override
    public void updateAnimalAdded (Animal animal) {
        // Increment the number of animals
        animalsAddedCount.incrementAndGet();
        // Print the number of animals
        System.out.println("Total animals added: " + animalsAddedCount);
    }
}

方法同步解決方案代碼如下:

public class CountingAnimalAddedListener implements AnimalAddedListener {
    private static int animalsAddedCount = 0;
    @Override
    public synchronized void updateAnimalAdded (Animal animal) {
        // Increment the number of animals
        animalsAddedCount++;
        // Print the number of animals
        System.out.println("Total animals added: " + animalsAddedCount);
    }
}

要強(qiáng)調(diào)的是監(jiān)聽器應(yīng)該保證自身的線程安全,subject需要理解監(jiān)聽器的內(nèi)部邏輯,而不是簡(jiǎn)單確保對(duì)監(jiān)聽器的訪問和修改的線程安全。否則,如果多 個(gè)subject共用同一個(gè)監(jiān)聽器,那每個(gè)subject類都要重寫一遍線程安全的代碼,顯然這樣的代碼不夠簡(jiǎn)潔,因此需要在監(jiān)聽器類內(nèi)實(shí)現(xiàn)線程安全。

監(jiān)聽器的有序通知

當(dāng) 要求監(jiān)聽器有序執(zhí)行時(shí),讀寫鎖就不能滿足需求了,而需要引入一個(gè)新的機(jī)制,可以保證notify函數(shù)的調(diào)用順序和animal添加到zoo的順序一致。有 人嘗試過用方法同步來(lái)實(shí)現(xiàn),然而根據(jù)Oracle文檔中的方法同步介紹,可知方法同步并不提供操作執(zhí)行的順序管理。它只是保證原子操作,也就是說操作不會(huì) 被打斷,并不能保證先來(lái)先執(zhí)行(FIFO)的線程順序。ReentrantReadWriteLock可以實(shí)現(xiàn)這樣的執(zhí)行順序,代碼如下:

public class OrderedThreadSafeZoo {
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);
    protected final Lock readLock = readWriteLock.readLock();
    protected final Lock writeLock = readWriteLock.writeLock();
    private List<animal> animals = new ArrayList<>();
    private List<animaladdedlistener> listeners = new ArrayList<>();
    public void addAnimal (Animal animal) {
        // Add the animal to the list of animals
        this.animals.add(animal);
        // Notify the list of registered listeners
        this.notifyAnimalAddedListeners(animal);
    }
    public AnimalAddedListener registerAnimalAddedListener (AnimalAddedListener listener) {
        // Lock the list of listeners for writing
        this.writeLock.lock();
        try {
            // Add the listener to the list of registered listeners
            this.listeners.add(listener);
        }
        finally {
            // Unlock the writer lock
            this.writeLock.unlock();
        }
        return listener;
    }
    public void unregisterAnimalAddedListener (AnimalAddedListener listener) {
        // Lock the list of listeners for writing
        this.writeLock.lock();
        try {
            // Remove the listener from the list of the registered listeners
            this.listeners.remove(listener);
        }
        finally {
            // Unlock the writer lock
            this.writeLock.unlock();
        }
    }
    public void notifyAnimalAddedListeners (Animal animal) {
        // Lock the list of listeners for reading
        this.readLock.lock();
        try {
            // Notify each of the listeners in the list of registered listeners
            this.listeners.forEach(listener -> listener.updateAnimalAdded(animal));
        }
        finally {
            // Unlock the reader lock
            this.readLock.unlock();
        }
    }
}

這樣的實(shí)現(xiàn)方式,register, unregister和notify函數(shù)將按照先進(jìn)先出(FIFO)的順序獲得讀寫鎖權(quán)限。例如,線程1注冊(cè)一個(gè)監(jiān)聽器,線程2在開始執(zhí)行注冊(cè)操作后試圖 通知已注冊(cè)的監(jiān)聽器,線程3在線程2等待只讀鎖的時(shí)候也試圖通知已注冊(cè)的監(jiān)聽器,采用fair-ordering方式,線程1先完成注冊(cè)操作,然后線程2 可以通知監(jiān)聽器,最后線程3通知監(jiān)聽器。這樣保證了action的執(zhí)行順序和開始順序一致。

如果采用方法同步,雖然線程2先排隊(duì)等待占用資 源,線程3仍可能比線程2先獲得資源鎖,而且不能保證線程2比線程3先通知監(jiān)聽器。問題的關(guān)鍵所在:fair-ordering方式可以保證線程按照申請(qǐng) 資源的順序執(zhí)行。讀寫鎖的順序機(jī)制很復(fù)雜,應(yīng)參照ReentrantReadWriteLock的官方文檔以確保鎖的邏輯足夠解決問題。

截止目前實(shí)現(xiàn)了線程安全,在接下來(lái)的章節(jié)中將介紹提取主題的邏輯并將其mixin類封裝為可重復(fù)代碼單元的方式優(yōu)缺點(diǎn)。

主題邏輯封裝到Mixin類

把 上述的觀察者模式設(shè)計(jì)實(shí)現(xiàn)封裝到目標(biāo)的mixin類中很具吸引力。通常來(lái)說,觀察者模式中的觀察者包含已注冊(cè)的監(jiān)聽器的集合;負(fù)責(zé)注冊(cè)新的監(jiān)聽器的 register函數(shù);負(fù)責(zé)撤銷注冊(cè)的unregister函數(shù)和負(fù)責(zé)通知監(jiān)聽器的notify函數(shù)。對(duì)于上述的動(dòng)物園的例子,zoo類除動(dòng)物列表是問題 所需外,其他所有操作都是為了實(shí)現(xiàn)主題的邏輯。

Mixin類的案例如下所示,需要說明的是為使代碼更為簡(jiǎn)潔,此處去掉關(guān)于線程安全的代碼:

public abstract class ObservableSubjectMixin<listenertype> {
    private List<listenertype> listeners = new ArrayList<>();
    public ListenerType registerListener (ListenerType listener) {
        // Add the listener to the list of registered listeners
        this.listeners.add(listener);
        return listener;
    }
    public void unregisterAnimalAddedListener (ListenerType listener) {
        // Remove the listener from the list of the registered listeners
        this.listeners.remove(listener);
    }
    public void notifyListeners (Consumer<!--? super ListenerType--> algorithm) {
        // Execute some function on each of the listeners
        this.listeners.forEach(algorithm);
    }
}

正因?yàn)闆]有提供正在注冊(cè)的監(jiān)聽器類型的接口信息,不能直接通知某個(gè)特定的監(jiān)聽器,所以正需要保證通知功能的通用性,允許客戶端添加一些功能,如接受泛型參數(shù)類型的參數(shù)匹配,以適用于每個(gè)監(jiān)聽器,具體實(shí)現(xiàn)代碼如下:

public class ZooUsingMixin extends ObservableSubjectMixin<animaladdedlistener> {
    private List<animal> animals = new ArrayList<>();
    public void addAnimal (Animal animal) {
        // Add the animal to the list of animals
        this.animals.add(animal);
        // Notify the list of registered listeners
        this.notifyListeners((listener) -> listener.updateAnimalAdded(animal));
    }
}

Mixin類技術(shù)的最大優(yōu)勢(shì)是把觀察者模式的Subject封裝到一個(gè)可重復(fù)調(diào)用的類中,而不是在每個(gè)subject類中都重復(fù)寫這些邏輯。此外,這一方法使得zoo類的實(shí)現(xiàn)更為簡(jiǎn)潔,只需要存儲(chǔ)動(dòng)物信息,而不用再考慮如何存儲(chǔ)和通知監(jiān)聽器。

然 而,使用mixin類并非只有優(yōu)點(diǎn)。比如,如果要存儲(chǔ)多個(gè)類型的監(jiān)聽器怎么辦?例如,還需要存儲(chǔ)監(jiān)聽器類型AnimalRemovedListener。 mixin類是抽象類,Java中不能同時(shí)繼承多個(gè)抽象類,而且mixin類不能改用接口實(shí)現(xiàn),這是因?yàn)榻涌诓话瑂tate,而觀察者模式中state 需要用來(lái)保存已經(jīng)注冊(cè)的監(jiān)聽器列表。

其中的一個(gè)解決方案是創(chuàng)建一個(gè)動(dòng)物增加和減少時(shí)都會(huì)通知的監(jiān)聽器類型ZooListener,代碼如下所示:

public interface ZooListener {
    public void onAnimalAdded (Animal animal);
    public void onAnimalRemoved (Animal animal);
}

這樣就可以使用該接口實(shí)現(xiàn)利用一個(gè)監(jiān)聽器類型對(duì)zoo狀態(tài)各種變化的監(jiān)聽了:

public class ZooUsingMixin extends ObservableSubjectMixin<zoolistener> {
    private List<animal> animals = new ArrayList<>();
    public void addAnimal (Animal animal) {
        // Add the animal to the list of animals
        this.animals.add(animal);
        // Notify the list of registered listeners
        this.notifyListeners((listener) -> listener.onAnimalAdded(animal));
    }
    public void removeAnimal (Animal animal) {
        // Remove the animal from the list of animals
        this.animals.remove(animal);
        // Notify the list of registered listeners
        this.notifyListeners((listener) -> listener.onAnimalRemoved(animal));
    }
}

將多個(gè)監(jiān)聽器類型合并到一個(gè)監(jiān)聽器接口中確實(shí)解決了上面提到的問題,但仍舊存在不足之處,接下來(lái)的章節(jié)會(huì)詳細(xì)討論。

Multi-Method監(jiān)聽器和適配器

在 上述方法,監(jiān)聽器的接口中實(shí)現(xiàn)的包含太多函數(shù),接口就過于冗長(zhǎng),例如,Swing MouseListener就包含5個(gè)必要的函數(shù)。盡管可能只會(huì)用到其中一個(gè),但是只要用到鼠標(biāo)點(diǎn)擊事件就必須要添加這5個(gè)函數(shù),更多可能是用空函數(shù)體來(lái) 實(shí)現(xiàn)剩下的函數(shù),這無(wú)疑會(huì)給代碼帶來(lái)不必要的混亂。

其中一種解決方案是創(chuàng)建適配器(概念來(lái)自GoF提出的適配器模式),適配器中以抽象函數(shù) 的形式實(shí)現(xiàn)監(jiān)聽器接口的操作,供具體監(jiān)聽器類繼承。這樣一來(lái),具體監(jiān)聽器類就可以選擇其需要的函數(shù),對(duì)adapter不需要的函數(shù)采用默認(rèn)操作即可。例如 上面例子中的ZooListener類,創(chuàng)建ZooAdapter(Adapter的命名規(guī)則與監(jiān)聽器一致,只需要把類名中的Listener改為 Adapter即可),代碼如下:

public class ZooAdapter implements ZooListener {
    @Override
    public void onAnimalAdded (Animal animal) {}
    @Override
    public void onAnimalRemoved (Animal animal) {}
}

乍一看,這個(gè)適配器類微不足道,然而它所帶來(lái)的便利卻是不可小覷的。比如對(duì)于下面的具體類,只需選擇對(duì)其實(shí)現(xiàn)有用的函數(shù)即可:

public class NamePrinterZooAdapter extends ZooAdapter {
    @Override
    public void onAnimalAdded (Animal animal) {
        // Print the name of the animal that was added
        System.out.println("Added animal named " + animal.getName());
    }
}

有兩種替代方案同樣可以實(shí)現(xiàn)適配器類的功能:一是使用默認(rèn)函數(shù);二是把監(jiān)聽器接口和適配器類合并到一個(gè)具體類中。默認(rèn)函數(shù)是Java8新提出的,在接口中允許開發(fā)者提供默認(rèn)(防御)的實(shí)現(xiàn)方法。

Java 庫(kù)的這一更新主要是方便開發(fā)者在不改變老版本代碼的情況下,實(shí)現(xiàn)程序擴(kuò)展,因此應(yīng)該慎用這個(gè)方法。部分開發(fā)者多次使用后,會(huì)感覺這樣寫的代碼不夠?qū)I(yè),而 又有開發(fā)者認(rèn)為這是Java8的特色,不管怎樣,需要明白這個(gè)技術(shù)提出的初衷是什么,再結(jié)合具體問題決定是否要用。使用默認(rèn)函數(shù)實(shí)現(xiàn)的 ZooListener接口代碼如下示:

public interface ZooListener {
    default public void onAnimalAdded (Animal animal) {}
    default public void onAnimalRemoved (Animal animal) {}
}

通過使用默認(rèn)函數(shù),實(shí)現(xiàn)該接口的具體類,無(wú)需在接口中實(shí)現(xiàn)全部函數(shù),而是選擇性實(shí)現(xiàn)所需函數(shù)。雖然這是接口膨脹問題一個(gè)較為簡(jiǎn)潔的解決方案,開發(fā)者在使用時(shí)還應(yīng)多加注意。

第二種方案是簡(jiǎn)化觀察者模式,省略了監(jiān)聽器接口,而是用具體類實(shí)現(xiàn)監(jiān)聽器的功能。比如ZooListener接口就變成了下面這樣:

public class ZooListener {
    public void onAnimalAdded (Animal animal) {}
    public void onAnimalRemoved (Animal animal) {}
}

這一方案簡(jiǎn)化了觀察者模式的層次結(jié)構(gòu),但它并非適用于所有情況,因?yàn)槿绻驯O(jiān)聽器接口合并到具體類中,具體監(jiān)聽器就不可以實(shí)現(xiàn)多個(gè)監(jiān)聽接口了。例 如,如果AnimalAddedListener和AnimalRemovedListener接口寫在同一個(gè)具體類中,那么單獨(dú)一個(gè)具體監(jiān)聽器就不可以 同時(shí)實(shí)現(xiàn)這兩個(gè)接口了。此外,監(jiān)聽器接口的意圖比具體類更顯而易見,很顯然前者就是為其他類提供接口,但后者就并非那么明顯了。

如果沒有合適的文檔說明,開發(fā)者并不會(huì)知道已經(jīng)有一個(gè)類扮演著接口的角色,實(shí)現(xiàn)了其對(duì)應(yīng)的所有函數(shù)。此外,類名不包含adapter,因?yàn)轭惒⒉贿m配于某一個(gè)接口,因此類名并沒有特別暗示此意圖。綜上所述,特定問題需要選擇特定的方法,并沒有哪個(gè)方法是萬(wàn)能的。

在 開始下一章前,需要特別提一下,適配器在觀察模式中很常見,尤其是在老版本的Java代碼中。Swing API正是以適配器為基礎(chǔ)實(shí)現(xiàn)的,正如很多老應(yīng)用在Java5和Java6中的觀察者模式中所使用的那樣。zoo案例中的監(jiān)聽器或許并不需要適配器,但需 要了解適配器提出的目的以及其應(yīng)用,因?yàn)槲覀兛梢栽诂F(xiàn)有的代碼中對(duì)其進(jìn)行使用。下面的章節(jié),將會(huì)介紹時(shí)間復(fù)雜的監(jiān)聽器,該類監(jiān)聽器可能會(huì)執(zhí)行耗時(shí)的運(yùn)算或 進(jìn)行異步調(diào)用,不能立即給出返回值。

Complex & Blocking監(jiān)聽器

關(guān)于觀察者模式的一個(gè)假設(shè)是: 執(zhí)行一個(gè)函數(shù)時(shí),一系列監(jiān)聽器會(huì)被調(diào)用,但假定這一過程對(duì)調(diào)用者而言是完全透明的。例如,客戶端代碼在Zoo中添加animal時(shí),在返回添加成功之前, 并不知道會(huì)調(diào)用一系列監(jiān)聽器。如果監(jiān)聽器的執(zhí)行需要時(shí)間較長(zhǎng)(其時(shí)間受監(jiān)聽器的數(shù)量、每個(gè)監(jiān)聽器執(zhí)行時(shí)間影響),那么客戶端代碼將會(huì)感知這一簡(jiǎn)單增加動(dòng)物 操作的時(shí)間副作用。

本文不能面面俱到的討論這個(gè)話題,下面幾條是開發(fā)者調(diào)用復(fù)雜的監(jiān)聽器時(shí)應(yīng)該注意的事項(xiàng):

  1. 監(jiān)聽器啟動(dòng)新線程。新線程啟動(dòng)后,在新線程中執(zhí)行監(jiān)聽器邏輯的同時(shí),返回監(jiān)聽器函數(shù)的處理結(jié)果,并運(yùn)行其他監(jiān)聽器執(zhí)行。

2. Subject啟動(dòng)新線程。與傳統(tǒng)的線性迭代已注冊(cè)的監(jiān)聽器列表不同,Subject的notify函數(shù)重啟一個(gè)新的線程,然后在新線程中迭代監(jiān)聽器列 表。這樣使得notify函數(shù)在執(zhí)行其他監(jiān)聽器操作的同時(shí)可以輸出其返回值。需要注意的是需要一個(gè)線程安全機(jī)制來(lái)確保監(jiān)聽器列表不會(huì)進(jìn)行并發(fā)修改。

3. 隊(duì)列化監(jiān)聽器調(diào)用并采用一組線程執(zhí)行監(jiān)聽功能。將監(jiān)聽器操作封裝在一些函數(shù)中并隊(duì)列化這些函數(shù),而非簡(jiǎn)單的迭代調(diào)用監(jiān)聽器列表。這些監(jiān)聽器存儲(chǔ)到隊(duì)列中 后,線程就可以從隊(duì)列中彈出單個(gè)元素并執(zhí)行其監(jiān)聽邏輯。這類似于生產(chǎn)者-消費(fèi)者問題,notify過程產(chǎn)生可執(zhí)行函數(shù)隊(duì)列,然后線程依次從隊(duì)列中取出并執(zhí) 行這些函數(shù),函數(shù)需要存儲(chǔ)被創(chuàng)建的時(shí)間而非執(zhí)行的時(shí)間供監(jiān)聽器函數(shù)調(diào)用。例如,監(jiān)聽器被調(diào)用時(shí)創(chuàng)建的函數(shù),那么該函數(shù)就需要存儲(chǔ)該時(shí)間點(diǎn),這一功能類似于 Java中的如下操作:

public class AnimalAddedFunctor {
    private final AnimalAddedListener listener;
    private final Animal parameter;
    public AnimalAddedFunctor (AnimalAddedListener listener, Animal parameter) {
        this.listener = listener;
        this.parameter = parameter;
    }
    public void execute () {
        // Execute the listener with the parameter provided during creation
        this.listener.updateAnimalAdded(this.parameter);
    }
}

函數(shù)創(chuàng)建并保存在隊(duì)列中,可以隨時(shí)調(diào)用,這樣一來(lái)就無(wú)需在遍歷監(jiān)聽器列表時(shí)立即執(zhí)行其對(duì)應(yīng)操作了。一旦每個(gè)激活監(jiān)聽器的函數(shù)都?jí)喝腙?duì)列中,“消費(fèi)者 線程”就會(huì)給客戶端代碼返回操作權(quán)。之后某個(gè)時(shí)間點(diǎn)“消費(fèi)者線程”將會(huì)執(zhí)行這些函數(shù),就像在監(jiān)聽器被notify函數(shù)激活時(shí)執(zhí)行一樣。這項(xiàng)技術(shù)在其他語(yǔ)言 中被叫作參數(shù)綁定,剛好適合上面的例子,技術(shù)的實(shí)質(zhì)是保存監(jiān)聽器的參數(shù),execute()函數(shù)再直接調(diào)用。如果監(jiān)聽器接收多個(gè)參數(shù),處理方法也類似。

需 要注意的是如果要保存監(jiān)聽器的執(zhí)行順序,則需要引入綜合排序機(jī)制。方案一中,監(jiān)聽器按照正常的順序激活新線程,這樣可以確保監(jiān)聽器按照注冊(cè)的順序執(zhí)行。方 案二中,隊(duì)列支持排序,其中的函數(shù)會(huì)按照進(jìn)入隊(duì)列的順序執(zhí)行。簡(jiǎn)單來(lái)說就是,開發(fā)者需要重視監(jiān)聽器多線程執(zhí)行的復(fù)雜程度,加以小心處理以確保實(shí)現(xiàn)所需的功 能。

結(jié)束語(yǔ)

觀察者模式在1994年被寫進(jìn)書中以前,就已經(jīng)是主流的軟件設(shè)計(jì)模式了,為軟件設(shè)計(jì)中經(jīng)常出現(xiàn)的問題提供了很 多令人滿意的解決方案。Java一直是使用該模式的引領(lǐng)者,在其標(biāo)準(zhǔn)庫(kù)中封裝了這一模式,但是鑒于Java更新到了版本8,十分有必要重新考查經(jīng)典模式在 其中的使用。隨著lambda表達(dá)式和其他新結(jié)構(gòu)的出現(xiàn),這一“古老的”模式又有了新的生機(jī)。無(wú)論是處理舊程序還是使用這一歷史悠久的方法解決新問題,尤 其對(duì)經(jīng)驗(yàn)豐富的Java開發(fā)者來(lái)說,觀察者模式都是開發(fā)者的主要工具。

原文鏈接:

譯者:王鵬,OneAPM工程師

順帶提個(gè)好消息,


標(biāo)簽:JavaMyEclipse

本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@ke049m.cn


為你推薦

  • 推薦視頻
  • 推薦活動(dòng)
  • 推薦產(chǎn)品
  • 推薦文章
  • 慧都慧問
掃碼咨詢


添加微信 立即咨詢

電話咨詢

客服熱線
023-68661681

TOP
利記足球官網(wǎng)(官方)網(wǎng)站/網(wǎng)頁(yè)版登錄入口/手機(jī)版登錄入口-最新版(已更新) 真人boyu·博魚滾球網(wǎng)(官方)網(wǎng)站/網(wǎng)頁(yè)版登錄入口/手機(jī)版登錄入口-最新版(已更新) 最大網(wǎng)上PM娛樂城盤口(官方)網(wǎng)站/網(wǎng)頁(yè)版登錄入口/手機(jī)版登錄入口-最新版(已更新) 正規(guī)雷火競(jìng)技官方買球(官方)網(wǎng)站/網(wǎng)頁(yè)版登錄入口/手機(jī)版登錄入口-最新版(已更新) 雷火競(jìng)技權(quán)威十大網(wǎng)(官方)網(wǎng)站/網(wǎng)頁(yè)版登錄入口/手機(jī)版登錄入口-最新版(已更新) boyu·博魚信譽(yù)足球官網(wǎng)(官方)網(wǎng)站/網(wǎng)頁(yè)版登錄入口/手機(jī)版登錄入口-最新版(已更新) 權(quán)威188BET足球網(wǎng)(官方)網(wǎng)站/網(wǎng)頁(yè)版登錄入口/手機(jī)版登錄入口-最新版(已更新) 正規(guī)188BET足球大全(官方)網(wǎng)站/網(wǎng)頁(yè)版登錄入口/手機(jī)版登錄入口-最新版(已更新) 国产午夜无码片在 | 成人欧美在线观看 | 97人妻无码免费视频一区 | 成人午夜福利电影天堂 | 国产女主播真实视频在线观看 | 精品熟女一区 | 成人小说亚洲一区二区三区 | 3d动漫精品啪啪一区二区 | 丰满人妻啪啪 | 高清无码一区二区在线观看吞精 | 国产精品日韩欧美制服 | 国产自产v一区二区三区c按摩 | 丰满人妻一区二区三区免费视频 | 国产91av视| 国产人妻无码 | 国产成人久久精品麻豆二区 | 精品国内自产拍在线看99 | 国产无套粉嫩流白浆不卡 | 精品国产一区二区三区无码乌克兰 | 国产国语对白在线视频 | 国产灌醉视频一区二区 | 成人精品在线 | a级毛片毛片免费观的看久 a级毛片毛片免费观看久潮喷 | aⅴ一区二区三区无卡无码 aⅴ在线观看视频 | 成人av在线观看网站一区二区 | 91精品久久久久 | av无码精品久久久久精品免费 | av无码国产麻豆映画传媒 | 国产在线成人 | 岛国精品在线 | 国产在线观看精品福利片 | 国产色午夜婷婷一区二区三区 | 激情啪啪精品一区二区 | 国产精品白浆一区二区三 | 国产一区二区三区不卡在线观 | 精品国产sm捆绑最大网免费 | 国产三级片久久久久久水户香奈 | 国产一区二区精品人妖系列 | 国产欧美久久影视 | 成人无码a区在线观看视频 成人无码h动漫在线网站免费y | 国产成人自啪精品视频 | 国产一区二在线观看 | 91在线无码精品秘入口色欲 | 精品国偷自产在线视频99 | 国产制服丝袜欧美在线观看 | 国产成人麻豆亚洲综合无码精 | av无码精品一区二区三区三级 | 国产精品成人久久 | 国产成人av激 | 不卡无码在线观看视色 | 国模精品一区三区 | a级毛片无码无遮挡内射 | 国产黄色片三级 | 国产福利91精品一区 | 国模无码视频一区 | 国产三级电影hd在线观看 | 国産精品久久久久久久 | 国产91久久久久久久免费 | av人人澡人人爽人人夜夜 | 1024国产精品一区在线观看 | 国产3p露脸| 高清在线一区二区三区亚洲 | 国产精品黑色丝 | 国产avxxxx无套内射 | 国产精品欧美v片免费观看 国产精品欧美福利 | 丰满老熟妇好大bbbbb | 精品无码成人 | 国产精品精华液网站 | 国产91小视频在线观看 | 国产成人精品综合网站 | 国产精品亚洲综合一区在线观 | 国内第一永久免费福利视频 | 国产一区二区免费视频 | 18禁无码无遮挡在线播放 | 国产精品无码久久专区 | 国产av国产精品白丝jk制服 | 国产一区精品在线观看 | 精品不卡免费一区二区三区 | 国产麻豆成人传媒免费观看 | 国产成人片无码免费视频软件 | 国产蜜桃扣扣传媒av性色 | 97色偷偷色噜噜男人 | 国产一区二区三区自产 | 国产成人黄色网站视频在线观看 | 韩国美女主播热舞 | 国产成人黄网在线免 | 激情都市综亚洲精品综合 | 成人毛片无码免费播放网站 | 国产亚洲日韩中文字幕欧美视频 | 国产午夜精品久久久久久久 | 国产精品蜜臂在线观看 | 东京热加勒比国产精品 | 91无码人区精品一区二区三区 | 国产福利一区二区精品免费 | 国产91久久九九免费精品无码 | 国产一区二区女内射 | 国产成人高清在线播放 | 91麻豆精品国产自产在线观看一 | 精品亚洲一区二区三区在线 | 国人av偷拍盗摄摄像 | 91视频播放 | 高潮一区二区三区四区在线播 | 国产精品国产三级国产aⅴ9色 | 1024国产精品一区在线观看 | 动漫福利精品一 | 国产成人一区免费观看 | 国产精品白丝久久av情趣网站 | 成人无码潮喷视频 | 国产69精品久久久久999小说 | 国产精品无码影院av | 国产一区在线观看视频 | 国产精品国语自产拍在线观看 | 国产综合精品久久 | 国产福利无码一区二区在线不 | 国产人伦精品一区二区三区 | 91精品啪在线观看国产色 | av三级片在线 | 精品人妻无码视频网站 | av鲁丝一区二区三区 | 国产精品麻豆天美精品久久 | 国产高潮流白浆喷水在线观看 | 国产欧美日韩视频在线观看一区 | 国产成人精品亚洲 | 97人妻免费专区 | 国产欧美日韩综合一区在线播放 | 18禁美女黄网站色大片在线 | 成人国产一区二区三区久久久 | 国内精品伊人久久久av高清影 | 国产精品偷伦视频免费观看 | 国产成人精品久久亚 | 国产在线永久视频免费下载 | 高清不卡亚洲日韩av在线 | av巨作精品原创 | 国产成人av网站手机不卡 | 国产成人精品视频一区二区不卡 | 国产v一区二区三区日 | av无码精品一区二区久久 | 国产99精品亚洲 | 国产尤物在线精品一区 | 2025国产大陆天免费看黄色视频 | 99精品一区二区三区视频 | 91亚洲自偷观看高清 | 国产av无码专区亚洲av麻豆丫 | 91精品福利观看 | 国产一区二区三区久久精品小说 | 91嫩草国产在线无码观看 | 国产精品免费麻豆入口 | 国产精品v欧美精品v日韩精品 | 国产午夜精品一区二区三区四 | 韩国精品一区二区 | 精品日韩欧美人妻少妇 | 国产精品免费一区二区 | 国产国产久热这里只有精品 | 国产午夜小视频在线 | 99国产欧美另类久久久精品 | 加勒比色综合久久久久久久久 | 成在线人免费视频播放 | 国产精品色婷婷99久久精品 | 2025最新国产不卡a在线播放 | 韩国福利一区二区三区高清视频 | 国产在线五月综合婷婷 | 成午夜精品一区二区三区秒播 | 国产亚洲欧美在手机线专区 | 国产亚洲中文久久网久久综合 | 国模无码一区二区三区在线观看 | 91极品女神私人尤物在线播放 | 国产夫妻自拍91 | 国产精品白丝av在线观看播放 | 动漫日韩无码一区 | 成人黄网18免费观看的网站 | 成人午夜电影在线播放网 | 精品视频在线观看一区二区 | 国产一区在线观看不卡 | 成人精品妖精视频在线观看 | 国产在线拍小情侣国产拍拍偷 | 国产成人啪精品 | 国产精品一v二v在线观看 | 国产精品香蕉在线的人 | 18精品久久久无码午夜福 | 国产午夜福利片国产一级a片 | 91麻豆精品国产自产 | 国产成人精品午夜视频 | 国产萌白酱在线一区二区 | av免费播放一区二 | 成年女人免费视频拍拍拍 | 精品国产你懂的在线 | 国内偷自第一区二区三区 | 91极品视频在线观看 | 动漫美女h黄动漫在线观看 动漫美女爆羞羞动漫 | 2025女人天堂在线观看 | av天堂精品久久久久 | 加勒比中文字幕无码久久 | 国产夫妻一区二区 | 精品麻豆福利片国产免费观看 | 99久久香蕉国产综合影院 | 成人爽a毛片免费视频 | 国产麻豆a一级毛片爽爽影院 | 国产一级内射一片 | 国语自产偷拍精品视频偷最新 | 国产精品重口变态sm在线观看 | 韩国三级a视频在线观看 | 91日韩精品视频 | 97人妻视频免费 | 国产成人手机高清在线观看网站 | 97人人添人澡人人爽超碰 | 国产精品高清一区二区不卡 | 波多野结衣永久免费视频 | 91精品一区二区三区在线播放 | 黑人巨大精品欧美一区二区 | 国产熟女亚洲精品麻豆 | 国产精品一二三次视频 | 国产91精品在线观看导航 | 国产日韩aⅴ无码一区二区三区 | 韩国午夜理伦三级在线观看仙踪林 | 国产+日韩+另类+视频一区 | 国产午夜福利不卡免费播放 | 国产成人在线播放视频 | 东京热一区二区无码视频 | 国产精品麻豆专区 | av毛片免费看电 | 国产成人亚洲精品无码av大片 | 精品视频一区二区三区在线观看 | 成人午夜福利电影天堂 | 国产情趣网站视频在线观看 | 妓女妓女一区二区三 | 91亚洲视频 | 国产日韩欧美一区二区综合区 | 国产亚洲欧美日韩综合另类 | 精品国产制服丝袜高跟 | 国模极品一区二区三区 | 国产偷窥熟女精品视频 | 国产91在线欧美 | 2025年日本伦理片村庄 | 成人欧美日韩一区二区三区 | 成人精品视频一区二区三区不卡 | 国产在线精品一区免费香 | 国产亚洲日本人在线观看 | 国产尤物在线无码福利网 | 国产种子在线看网站在线观看 | 国产精品爆乳在线播放 | 精品人妻少妇一区二区a | 国产精品美女www爽爽爽软件 | 99精品欧美美女福到在线不卡 | 国产精品无码制服 | 精品无码免费视频 | 国产成人涩涩涩视频在线观看 | 国产高清国内精品福利99久久 | 国产精品白丝jk白祙喷水视频 | 91精品国产自产 | 精品国产v无码大片在线观看 | 国产福利小视频在线免费观看 | 91性色在| 国产系列丝袜熟女精品视频 | 国精品一区二区av无码中文字幕 | 国产精品男女猛烈高潮激情 | 国产一区二区四区在线 | 国产成人午夜福利电影在线观看者 | 国产成人精品美女在线 | 国产精品国产三级国av麻豆 | 国产精品白浆在线播放 | 国产精品成久久久久三级6二k | 国产精品亚洲专区无码破解版 | 国产重口老太和小伙乱 | 99精品国产一区二区电影 | 国产黑色丝袜视频在线观看网红 | 东京热人妻无码人avhd | 国产三级影片在线观看 | 精品国产午夜福利在线观看 | 精品亚洲a∨在线播放不卡 精品亚洲aⅴ无码午夜在线 | 精品国产午夜福利精品推荐 | 91一码二码区别在哪儿啊 | 国产成人毛片无码视频软件 | 精品日韩欧美人妻少妇 | 国产高清无码免费在线观看 | 97人人添人澡人人爽超碰 | 国产成人精品久久久久久久 | 国产精品伊人久久久 | 成人精品视频一区二区三区尤物 | av中文无码乱人伦在线观看 | 国产成人精品久久一区二区三 | 国产精品一区在线观看第一页 | 国产精品毛片a | 动漫精品中文字幕制服一区 | 国模无码视频一区二区三区 | 国产无码动漫一区二区三区 | 国产无码一区二区三区在线观看 | 国产欧美日韩视频免费61794 | 国产三级在线观看播放大学生 | 国产主播国内精品在线 | 精品视频在线视频观看 | 国色天香第01集在线播放 | 国产拍一二三四区在 | 国产一区二区三区在线视頻 | 国产精品毛片av一区二区亚洲 | 国产成人精选视频在线观看不卡 | 18禁无遮拦无码国产在线播放 | 国产精品无码不卡系列在线 | 国产在线观看av在线 | 91人妻人人澡人人爽人人精品 | 91天堂一区二区三区在线观看 | 国产艳妇av在线出轨 | 绯色一区二区 | 国产精品香蕉在线 | 国产精品成人一区 | 国产精品视频一二三四五 | 精品91自产拍在线观看二区 | 国产av无码专区亚洲av桃花庵 | 国产精品免费视频一区二区三 | 国产福利一区二区三区高清 | 国产三片理论电影在线 | 国产偷窥真人视频在线观看 | 国产av激情无 | 91欧美视频在线播放 | 国产在线不卡一区二区完整版 | 成人综合伊人五月婷久久 | 国产日韩一区在线精品 | 国产精品伊人久久免费视频 | 国产精品高潮呻吟 | 国产精品亚洲欧美日韩在线播放 | 国产精品区一区二区三在放 | 国产无码在线看免费 | av无码av在线a∨天堂毛片 | 国产在线观看午夜成人 | 丰满人妻熟妇乱又伦精品视频 | 国产区日韩欧美 | 国产精品毛片无遮挡 | 18禁无遮挡啪啪无码网站 | 调教人人传媒牛牛视频一区二区三区 | 国产成人综合在线观看 | 国产丝袜久久 | 国产精品偷窥熟女精品 | 爆乳熟妇一区二区三区霸乳 | 国产精品夜色视频一区 | 国精品人妻无码一区二区三区喝尿 | 国产成人无码午夜视频在线观 | 国产高清一区二区三区免 | 国产综合久久精品综合v无码 | 国产精品午夜福利免费 | 国产免费高清国产在线视频 | 国产自产v一区二区三区c | 91在线无码精品毛片 | 国产黄网站手机在线观看 | 国产喷潮[心城梦海] | av无码精品久久久久精品免费 | 91在线看 | 成人无码电影一区二区三区 | 97人妻在线视频观看 | 国产一区二区在线播放 | 精品熟人妻一区二区三区四区不卡 | 国产毛片久久久久久国产毛片无码 | 韩国无码av片在线观看网站 | 国产偷倩视频 | a级毛片高清免费视频就看 a级毛片高清免费视频在线 | 国产午夜亚洲精品不卡在线观看 | 91精品人妻一区二区三区蜜桃 | 911国产主播在线观看 | 91在线视频是否值得信赖 | 国产毛片大全视频 | 91久久精品无码一区二区免费 | 国产成人午夜在线视频a站 国产成人午夜在线视频免费 | 国产护士在病房av做爰小说 | 国产福利在线播放 | 大尺度毛片免费看 | 国产成人精品日本亚洲 | 国产在线精品99一区不卡 | 911日本亚洲精品 | 国产亚洲欧美在线 | av在线不卡无码 | 国产激情无码视频一区二区三区 | 国产日韩欧美高清一区二区三区 | 国产精品白浆大屁股一区二区三 | 成人国产精品秘久久久剧情紧凑 | 精品人妻一区二区三区有码 | 国产成人黄网在线免 | 国产精品无码一区二区在线国 | 国产一区二区丝袜高跟鞋 | 国产精品夜色视频一区 | 国产精品区av| 国产成人福利一区二区三区 | 国产av网站一区二区三区 | 国精品午夜福利视频不卡麻豆 | 国产91白丝在线播放 | 国产经典一区二区三区蜜芽 | 国产在线精品一区二区三 | 国产福利在线观看永久免费 | 3p撑开菊眼h| 国产精品全国免费观看高清 | 国产欧美精品一区二区三区四区 | 国产精品一级av片 | 国产av无码 | 精品精品国产自在香蕉网 | 国产肥妇无码精品视频 | 国产三级精品专区欧美激情福利 | 高清女同学巨大乳在线 | 国产成人av大片大片在线播 | 丰满的少妇愉情hd高清果冻传媒 | 精品99一区二区三区四区 | 成人日产国产av | 成人午夜看黄在线尤物成人 | 国产精品入口麻豆高清在线 | 精品国产中文在线二区 | 国产午夜激无码αv毛片久久 | 国产丝袜无码精品一区二区三区 | 成人无码精品1区2区3区 | 国产午夜精品亚洲精品国产 | 99精品久久久久中文字幕 | 91在线无码精品秘入口导航 | 国产精品日产精品久久 | 国产美女av毛片 | 韩国久久三级电影 | a人亚洲精v品无码樱花 | 精品国产香蕉伊思人在线又爽又黄 | 国产黄色片在线免费观看 | 国产日韩欧美馆免费观看 | 9191在线精品 | 国产成人麻豆精品午夜国产精 | 国产日韩一区在线观看麻卡 | 国产欧美日韩综合在线 | 国产精品对白交换绿帽视频 | 2025久久精品99精品久久 | 91欧美精品综合在线观看 | 精品国产免费第一区二区三区日 | av毛片| 丰满少妇人妻无码区 | 国产成人精品久久亚洲高清不卡p | 2025年精品国产福利在线 | 国产精品动漫无码1区 | 91久久国产综合精品女同国语 | 高潮流白浆潮喷在线播放视频 | 国产91丝袜在线观看 | 国产成人无码视频一区二区三 | 国产午夜鲁丝片av无码免费 | 成无码网在线观看 | 国产aⅴ一二三区无码视频 国产aⅴ一区 | 精品无人区乱码一区二区 | 91精品国产综合久久香蕉 | 国产在线精品99一区不卡 | 国产精品一区二区三区不卡视频 | 91精品极| 国产精品欧美亚洲制服 | 国产精品毛片无遮挡高清 | 国产av无码秘一区二区三区 | 激情丝袜欧美专区在线观看 | 精品少妇人妻av免费 | 91精品一区二区 | 91黑丝高跟 | 91久久国产高清 | 精品国产丝袜在线拍91 | 91乱码人妻精品一区二区三 | 18禁黄无遮挡免费网站国产 | 国产精品一级毛片无码老人 | 精品人妻少妇嫩草v无码专 精品人妻少妇无码视频 | 国产高清乱码无卡女 | 国产精品成人无码av无码免费 | 国产成人久久精品二区三区 | 国产成人片一区二区三区白 | 成人精品一区二区三区中文 | 国产偷伦精品视频 | 国产精品午夜福利不卡120 | 精品无码国产一区二区三区麻 | 国产91蜜芽在线观看 | 国产重口老太和小伙乱 | 国产一区二区三区韩国女主播 | 国产一区二区不卡老阿姨 | 精品久久久久久中文字幕无码网站 | 18禁免费无码 | 精品精品国产高清a级毛片8 | 国产在线无码尤物视频 | 国产精品视频一二三四五 | 91九色国产亚洲 | 国产每日精品 | 国产熟女高潮视频 | 国产高清美女一级a毛片久久w | 国产无码在线观看视频 | 国产在线无遮挡免费观看 | 国产成人毛片在线视频app | 国产高清一区二区 | 国产自啪精品视频网站丝袜 | 国产高清情侣高潮露脸 | 国产精品偷伦视频免费观看了 | 国产毛片一级在线 | 91麻豆产精品久久久久久夏晴 | www.中文字幕一区二区 | 国产99久久久国产精品潘金莲 | 国产精品四虎影视亚洲综合 | 97人妻人人揉人人躁人人 | 国产激情无码一区二区三区 | 国产午夜福利精品视频 | 国产成人综合vr | 高潮毛片无遮挡高清视频播放 | 国产成人3p视 | 丰满乱子伦无码专区 | 国产成人无码专区 | 18黑白丝水手服自慰喷水网站 | 成人午夜啪啪免费网站 | 高清日韩热门电影免费手机在线观看 | 国产一区二区久久 | 福利姬国产精品一区在线 | 国产女人高潮叫床男人桶到爽 | 国产成人免费高清av | 精品日本少妇免费 | 国产亚洲日韩网爆欧美 | 精品高清美女精品国产区 | 国产超爽人人爽人人做 | 国产漫画无码作爱视频免费 | 韩国三级电影久久久 | 国产三级精品三级在线播放 | 国产巨作麻豆欧美亚洲综合久久 | 99精品久久精品 | 精品亚洲av一区二区帮区 | 国产午夜福利片 | 91精品国产秘?在线观看app | 成人国产精品秘果冻传媒在线 | 国产午夜福利小视频 | 潮喷大喷水系列 | 国产91九色在线播放 | 国产一区二区视频在线观看 | 国产av无码片毛片一级 | 国产69精品久久久久妇女 | 成人无码电影在线 | 国产精品无码免费视频三四区 | 国产成人亚洲精品无码青青草原 | 福利精品一区二区三区 | 国产毛片在线视频a级 | 91免费国产在线观看 | 国产蜜桃色欲91精品一区二区 | 91全网在线观看国产 | 国产成人一区二区三区免费3 | 1000部啪啪未满十八勿入不卡 | 91久久人人妻人人澡人人爽 | 高潮一区二区三区四区在线播 | 国产人妖综合在线视频 | 国产人妖在线精品 | 国内精品视频一区国产 | 国产黑色丝袜在线播放 | 精品无码人妻一区二区三区 | 国产成人电影在线观看 | 精品视频无码专区在线观看 | 国产一区二区精品久久 | 国产福利95精品一区二区三区 | 国产黑色丝袜在线看片不卡顿 | 99国产精品欧美一区二区三区 | 国产精品va无码二区 | 成熟女人特级毛片www免费 | 国产一区二区三区丝袜精品 | 国产精品嫩草影院一二三区入口 | 国产高清精品一区二区不卡 | 99久久精品免费视频 | 国产剧mv免费软件麻花豆传煤 | 国产三级在线播放 | 海角视频(免费)在线观看 | 国产精品日日爱 | 精品久久黑丝高跟鞋 | 91露脸的极品国产系列高清 | 精品日韩欧美一区二区在线播放 | 国产精品高潮久久久久 | 国产亚洲综合无码一区二区 | 国产精品高潮久久久久无码av | 国产高清视频在线观看播放 | 国产精品亚洲美女久久久 | 精品日韩欧美一区在线播放不 | 国产成人无码a区在线观看视 | 国产精品高潮森林久久av无码 | 东京伊人一本到鬼色 | 国产毛片一区二区 | 国精品人妻无码一区二区三区 | 国产成人精品无码一区二区蜜柚 | 国产亚洲日韩欧美自拍另类 | 成人区人妻精品一区二区三区 | 国产成年码av片在线观看 | 国产人妖视频一区二区 | 成人免费毛片一区二区三区 | 国产精品日韩免费观看 | 国产一区二区在线视频观看 | 精品黑人一区二区三区 | 91精品在线观看视频 | 2025中文日产幕无线 | 国产精品成人无码av网站 | 99国产精品无码久久久久 | 国产一区在线观看无码av | a级毛片在线观看 | 国产国语精彩对白在线观看 | 国产va免费视频一区二区三区 | 国产精品成人免费精品自在线 | 国产精品亚洲欧美大片在线观看 | 69国产成人综合久久精品 | 91精品国产九九九九九九亚洲 | 国产精品国产三级国产专区5o | 国产精品日韩在线亚洲一区 | 国产麻豆欧美亚洲综合久久 | 国产成人亚洲精品无码av软件 | 国产精品人妻无码一区牛牛影视 | 成熟丰满熟妇高潮xxxxxa片 | 99国产在线国语精品2025 | 精品亚洲成在人线av无码 | 国产高清无码在线视频播放 | 国产3级在线观看 | 国产精品午夜在线 | 2025国自产拍精品高潮视频 | 国产午夜视频在 | 91精品国产全国免费观看蜜桃 | 高清无码中文专区 | 国产激情久久久久影院老熟女 | 国产精品视频一区二区三区 | 国产精品国产自线在线观看 | a级毛片免费全部播放经典 a级毛片免费全部播放无码 | 高清无码在国产极 | 丰满人妻一区二区三区免费视频 | 大爆乳双腿张开喷 | 91av剧情免费在线看片 | 国产a免费观看不卡 | 国产综合亚洲欧美日韩一区二区 | 成人精品午夜在线观看 | 国产免费8在线观看 | 国产午夜无码精品 | 91一区二区三区在线 | 国产成人无码一区a | 成人精品免费视频在线观看 | 国产91丝袜在线播放网站 | 国产宅男宅女在线观看 | 97人妻中| 国产成人精品免费一区 | 国产激情一区二区三区不卡 | 国产精品国产福利国产秒拍 | 成人国产欧美日韩在线观看 | 国产色偷丝袜婷婷无码中文 | 91丝袜在线观看 | 国产综合精品一区二 | 国产高清午夜成人在线观看 | 国产91在线精品不卡 | 国产成人亚洲精品无码青app | 国产丰满美女a级毛片 | av在线亚洲男 | 国产萌白酱喷水视频在线播放 | 精品福利一区二区在线 | 国产黑丝精品在线 | 国产精品国产三级国产av | 精品三级av无码一区 | 精品国产一区二区三区四区vr | 成人国内精品久久久久一区 | 成人婷婷网色偷偷亚洲男人 | 国产一区在线观看无码中文 | 韩国少妇激三级做爰在线观看 | 国产成人aⅴ片在线 | 国产成人免费无码av在线播放 | 国产一级无码免费a片 | 国产夫妻对 | 国产亚洲欧美精品久久久 | www.一区| 国产品精品无码视av | 国产在线观看一区二区三区精品 | ts清晰版在线观看 | 国产二区视频在线观看 | 国产福利麻豆精 | 国产超级aⅴ视频在线观看 国产超级a在线播放久日本 | 国产精品一区二区三区四区 | 激情人妻偷乱在线视频 | 成人无码在线视频网站 | 2025国产精品视频一区 | 国产sm系列在线观看 | 国产成人麻豆免费观看 | 91久久精品无码一区二区免费 | 国产av无码片毛片一级 | 国产av一区二区三区日韩 | 精品国产欧美一区二区三区 | 国产精品人成人免 | 精品国产av一二三四区 | 国产精品不卡一区二区三区 | 91在线无码精品毛片 | 波多野结衣在线精品视频 | 高清无码一区二区在线观看吞精 | 国产自卫香蕉久久 | 精品无码av无码免费专区 | 国产av无码日韩av无码av网站 | 91精品国产社区 | 国产麻豆亚洲精品 | 国产成人激烈叫床声视频对白 | 国产午夜av秒播在线观看 | 91精品久久久久久久久无码 | 91精品国产乱码在线观看 | 国产成人无码精品一区 | 99久久久无码国产精精品 | 东京热中文成av人片久久 | 99精品视频国产一区二区三区 | 精品日韩欧美一区二区三区 | 国产午夜福利精品无码 | 国产精品一区二区尿失禁 | 2025最新无码国 | 精品香蕉久久久午夜福利 | 国产三级国产三级欧美三级 | 99精品久久久久久久久久 | 国产精品视频一区二区噜噜 | 3d动漫精品啪啪一区 | 国产精品日日蜜臀 | 2025精品国偷自产免费观看 | 99久久国产精品免费电影 | 国产精品成人av电影不卡 | 国产精品国产自线拍免费丝 | 激情欧美日韩国产在线专区 | 国产成人午夜视频影院免费观看 | 国产无码第一页国产视频在 | 国产无套粉嫩白浆在线 | 国产午夜精品无码 | av无码精品一区二区久久 | 国产原创在线观看播放 | 2025久久精品国产99国产精品 | 国产欧美日韩另类专区 | 国产偷窥盗摄视频 | 国产黄色片在线观看 | 国产成人无码精品久久久露脸 | 爆爽av国产剧情精品原创一区 | 国产成人麻豆精品video | 2025色按摩电影在线观看 | 精品无码人妻被多人侵犯av | 国产精品视频在线观看 | 精品国产a∨无码一区二区三区 | 福利精品国产一区 | 国产激情一区二区三区成人91 | 国自产拍在线天天更新2025 | 成人国内精品视频在线观看 | 国产午夜在线观看免费 | 国产一区二区网曝门日韩 | 国产精品一久久香蕉国产线看观看 | a级孕妇高清免费毛片 | 国产高清女同学巨大乳在线观 | 岛国久久久久精品aaaa综合 | 91久久亚| 国产成在线观看免费视频密 | 国产成人小午夜视频在线观看 | 国产激情久久久久影 | 国产午夜精品一区二区三区嫩草 | 精品无人区一码卡二卡三 | 福利国产小视频 | 国产蜜桃国产蜜桃 | 精品无码国产自产在线观看老师 | 精品亚洲∨无码专区毛片 | 丰满少妇高潮惨叫久久久 | av片无码一区二区不卡电影 | 成人在线日韩 | 91精品久久人人妻人 | 18禁黄网站男男禁片免费观看 | 国产a级理论片免费播放 | 99精品网站 | 国产成人a在线观看视频 | 国产亚洲成v人片在线观黄桃 | 国产欧美另类久久久精品图片 | 后入内射国产一区二区 | 国产欧洲日韩一区二区三区在线观看 | 国产精品骚妇青草久久久久 | 高清无码电影免费专区 | 国产精品白浆一区二区三 | 囯产精品一区二 | 成人免费一区二区三区视频软件 | 精品国产一区二区三区吸毒 | 2025最新国产三级在线看 | 精品国产女同一区二区三区 | 高清无码国产免费片 | 国产成人片视频一区二区 | 国产乱aⅴ一区二区三区 | 国产超碰97久久人人操人人操 | 国产黄页网址大全免费 | 国产高潮呻吟无码精品av | 岛国久久久久精品aaaa综合 | 成人免费无码大片a毛片抽搐色欲 | 国产在线午夜不卡精品影院 | 国产一区二区国产精品三级 | 国产一级a毛看免费视频区二三 | 国产女同互慰久久亚洲 | 911国产影院在线观看 | 成人无码中文av天堂 | a人片在线观看 | 成在线人视频免费视频 | 国产精品欧美v片免费观看 国产精品欧美福利 | 精品免费av一区二区 | 国产一级毛片a午夜一级毛片 | 国产福利在线免费 | 国产精品国产三级国产普通话对白 | 国产不卡精品一区二区三区 | 3p国产对白刺激在线 | 国产aⅴ无码精品一区二区三区 | 国产天堂一区 | 97人妻精品全国免费视频 | 国产成人精品午夜在线播放 | 国产成人w一区二区三区综合 | 变态调教一区二区三区男同 | 国产三级黄片一区二区三区 | 国产91在线播放九色0000 | 精品亚洲一区二区三区四区五区 | 国产精品视频网国产 | 国产成人国产三级 | 国产精品亚洲无线码在线播放 | 18禁午夜福利a级污黄刺激 | 国产精品午夜无码av天美传 | 国产女人高潮特黄a毛片 | 国产美女一区二区三区 | 国语自产视频在线不卡 | 潮喷大喷水系列无码久久精品 | av中文字幕潮喷人妻系列 | 国产成人综合亚洲av成人专区 |