折騰Java設計模式之觀察者模式

觀察者模式

Define a one-to-many dependency between objects where a state change in one object results in all its dependents being notified and updated automatically.html

直譯過來就是,定義對象間的一對多依賴關係,當一個對象的狀態變動會自動通知和更新全部依賴項。像發佈/訂閱模式,事件通知模式,數據源/監聽模式等都是性質同樣。java

觀察者模式UML

UML類和時序圖

類圖:Subject類不會直接更新從屬對象的狀態。相反,Subject引用了用於更新狀態的觀察者接口(update()),這使得Subject獨立於依賴對象的狀態更新方式。Observer1和Observer2類經過將狀態與Subject的狀態同步來實現Observer接口。git

時序圖:Observer1和Observer2對象調用Subject1上的attach(this)來註冊本身。假如Subject1的狀態發生變動,Subject1自己調用notify()。notify()對已註冊的Observer1和Observer2對象調用update(),後者從Subject1請求已更改的數據(getState())以更新(同步)其狀態。github

img

UML類圖

img

觀察者模式角色

Subject(目標):目標又稱爲主題,它是指被觀察的對象。在目標中定義了一個觀察者集合,一個觀察目標能夠接受任意數量的觀察者來觀察,它提供一系列方法來增長和刪除觀察者對象,同時它定義了通知方法notify()。目標類能夠是接口,也能夠是抽象類或具體類。編程

ConcreteSubject(具體目標):具體目標是目標類的子類,一般它包含有常常發生改變的數據,當它的狀態發生改變時,向它的各個觀察者發出通知;同時它還實現了在目標類中定義的抽象業務邏輯方法(若是有的話)。若是無須擴展目標類,則具體目標類能夠省略。設計模式

Observer(觀察者):觀察者將對觀察目標的改變作出反應,觀察者通常定義爲接口,該接口聲明瞭更新數據的方法update(),所以又稱爲抽象觀察者。緩存

ConcreteObserver(具體觀察者):在具體觀察者中維護一個指向具體目標對象的引用,它存儲具體觀察者的有關狀態,這些狀態須要和具體目標的狀態保持一致;它實現了在抽象觀察者Observer中定義的update()方法。微信

觀察者模式描述瞭如何創建對象與對象之間的依賴關係,以及如何構造知足這種需求的系統。觀察者模式包含觀察目標和觀察者兩類對象,一個目標能夠有任意數目的與之相依賴的觀察者,一旦觀察目標的狀態發生改變,全部的觀察者都將獲得通知。做爲對這個通知的響應,每一個觀察者都將監視觀察目標的狀態以使其狀態與目標狀態同步,這種交互也稱爲發佈-訂閱(Publish-Subscribe)。觀察目標是通知的發佈者,它發出通知時並不須要知道誰是它的觀察者,能夠有任意數目的觀察者訂閱它並接收通知。異步

乾貨源碼解析

源碼地址編程語言

博客訂閱的功能,抽象主題中維護訂閱關係,同時引入普通和vip觀察者。

//抽象主題
@Data
public abstract class Subject {

    //主題訂閱者們
    private List<Observer> observerList = Lists.newArrayList();

    //訂閱
    public void register(Observer observer) {
        observerList.add(observer);
    }

    //取消訂閱
    public void remove(Observer observer) {
        observerList.remove(observer);
    }

    //發佈東西
    public abstract void publish(String msg);
}


//抽象觀察者
@Slf4j
@Data
@AllArgsConstructor
public abstract class Observer {

    //觀察者名稱
    private String name;

    //更新狀態,由主題調度
    public void update(Object subject, Object args) {
        log.info("{}獲取到變動通知:{}", name, args);
    }
}

//博客主題
@Slf4j
public class Blog extends Subject {

    @Override
    public void publish(String msg) {
        log.info("發佈msg:{}", msg);
        //通知訂閱者
        getObserverList().forEach(observer -> observer.update(this, msg));
    }
}

//普通用戶觀察者
@Slf4j
public class NormalObserver extends Observer {

    public NormalObserver(String name) {
        super(name);
    }

    @Override
    public void update(Object subject, Object args) {
        super.update(subject, args);
        log.info("{}獲取到變動通知:普通用戶能夠不緩存", getName());
    }
}

//vip用戶觀察者
@Slf4j
public class VipObserver extends Observer {

    public VipObserver(String name) {
        super(name);
    }

    @Override
    public void update(Object subject, Object args) {
        super.update(subject, args);
        log.info("{}獲取到變動通知:vip能夠緩存", getName());
    }
}


@Slf4j
public class Application {

    public static void main(String[] args) {
        Blog blog = new Blog();
        VipObserver wang = new VipObserver("老王99");
        VipObserver lee = new VipObserver("老李");
        NormalObserver four = new NormalObserver("小四");
        NormalObserver twoEgg = new NormalObserver("二蛋");
        log.info("---------------------begin--------------------");

        // 用戶訂閱博客,普通和vip用戶
        new Thread(() -> {
            blog.register(wang);
            sleep(2);
            blog.register(lee);
            sleep(2);
            blog.register(four);
        }).start();

        // 博客線程每隔2秒發佈一次文章, 總共發佈4次
        new Thread(() -> {
            IntStream.rangeClosed(1, 4).forEach(i -> {
                blog.publish(String.format("新把戲第%s次", i));
                sleep(2);
            });
        }).start();

        // 有用戶退出訂閱博客,也有二蛋加入訂閱
        new Thread(() -> {
            sleep(3);
            blog.remove(lee);
            sleep(2);
            blog.register(twoEgg);
        }).start();
    }

    private static void sleep(int seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            log.error("error : ", e);
        }
    }
}
複製代碼

image-20190124170206758

jdk內置的Obverser和Observable

@Slf4j
public class ObserverApplication {

    public static void main(String[] args) {
        log.info("Enter Text: ");
        EventSource eventSource = new EventSource();

        eventSource.addObserver((obj, arg) -> {
            log.info("Received response: {}", arg);
        });

        eventSource.addObserver((obj, arg) -> {
            log.info("Received response2: {},,{}", arg, obj
            );
        });

        new Thread(eventSource).start();
    }
}

class EventSource extends Observable implements Runnable {
    public void run() {
        while (true) {
            String response = new Scanner(System.in).next();
            setChanged();
            notifyObservers(response);
        }
    }
}
複製代碼

Java中的使用

它使得一個對象能夠靈活的將消息發送給感興趣的對象

java.util.EventListener

javax.servlet.http.HttpSessionBindingListener

javax.servlet.http.HttpSessionAttributeListener

javax.faces.event.PhaseListener

Listener從名字上看就明白是監聽的意思了。

JDK中內置的Obverser和Observable

jdk中內置的觀察者模式。便是java.util.Observer(接口)和java.util.Observable(類)。

簡單的說說這個。觀察者接口(java.util.Observer),主題(java.util.Observable)。實現觀察者接口和繼承主題。經過抽象主題的addObserver()註冊觀察者,deleteObserver()移除觀察者。

首先經過調用主題類的setChange()告知狀態變動,隨後調用notifyObservers方法(可傳可不傳參數)去通知觀察者,最後由於主題在notifyObservers時會主動調用觀察者的update()方法,改方法有2個參數,第一個爲主題對象,第二個爲可變參數。

總結

觀察者模式是一種使用頻率很是高的設計模式,JDK中就帶有實現。不管是移動應用、Web應用或者桌面應用,觀察者模式幾乎無處不在,它爲實現對象之間的聯動提供了一套完整的解決方案,凡是涉及到一對一或者一對多的對象交互場景均可以使用觀察者模式。觀察者模式普遍應用於各類編程語言的GUI事件處理的實現,在基於事件的XML解析技術(如SAX2)以及Web事件處理中也都使用了觀察者模式。

觀察者模式的優勢

(1) 觀察者模式能夠實現表示層和數據邏輯層的分離,定義了穩定的消息更新傳遞機制,並抽象了更新接口,使得能夠有各類各樣不一樣的表示層充當具體觀察者角色。 (2) 觀察者模式在觀察目標和觀察者之間創建一個抽象的耦合。觀察目標只須要維持一個抽象觀察者的集合,無須瞭解其具體觀察者。因爲觀察目標和觀察者沒有緊密地耦合在一塊兒,所以它們能夠屬於不一樣的抽象化層次。 (3) 觀察者模式支持廣播通訊,觀察目標會向全部已註冊的觀察者對象發送通知,簡化了一對多系統設計的難度。 (4) 觀察者模式知足「開閉原則」的要求,增長新的具體觀察者無須修改原有系統代碼,在具體觀察者與觀察目標之間不存在關聯關係的狀況下,增長新的觀察目標也很方便。

觀察者模式的缺點

(1) 若是一個觀察目標對象有不少直接和間接觀察者,將全部的觀察者都通知到會花費不少時間。 (2) 若是在觀察者和觀察目標之間存在循環依賴,觀察目標會觸發它們之間進行循環調用,可能致使系統崩潰。 (3) 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。

注意事項

一、JAVA 中已經有了對觀察者模式的支持類。 二、避免循環引用。 三、若是順序執行,某一觀察者錯誤會致使系統卡殼,通常採用異步方式。

參考

Observer pattern

觀察者模式|菜鳥教程

細數JDK裏的設計模式

設計模式總結(Java)—— 觀察者模式

歡迎關注

微信公衆號
相關文章
相關標籤/搜索