天天看點

設計模式-觀察者模式

我的了解

很久之前有個老師給我們講觀察者模式時,舉了一個例子:

媽媽照顧嬰兒,他們在不同的房間,嬰兒在嬰兒房間A,媽媽在書房M,

為了保證嬰兒睡醒之後能及時得到照顧,媽媽每隔10分鐘就去房間A瞧瞧,看看嬰兒是否醒了.

媽媽為了防止忘記,給自己定了一個鬧鐘,每隔10分鐘響一次.

while (10分鐘之後) {
      watchBaby();//檢查嬰兒是否睡醒
}           

一段時間之後,媽媽覺得很累,而且效率很低,因為很多次媽媽去照看嬰兒時,發現嬰兒在睡覺啥事也沒有.

後來,媽媽發現了一個好方法,在嬰兒身上寄一個鈴铛,嬰兒隻要一動,鈴铛就會響,媽媽就能聽到.

這樣媽媽就可以安心工作了,隻要聽到鈴铛響就跑過去照看下.

關系圖:

設計模式-觀察者模式

其實上面的例子涉及到兩種模式:

  1. 媽媽每隔10分鐘主動去檢視 是輪詢模式;
  2. 媽媽不用主動去檢查,嬰兒醒了,媽媽就會聽到鈴铛, 是觀察者模式

那種模式效率更高呢?

一般情況下,觀察者模式效率更高,

輪詢,每隔一段時間去檢視,浪費了資源.

觀察者模式的角色

有如下角色:觀察者,被觀察者,事件,

有如下操作:注冊,通知

觀察者

案例中的媽媽就是觀察者,她觀察嬰兒,看它醒了沒有,有什麼需求

被觀察者

嬰兒是被觀察者,是被關注的對象.

一旦它發生什麼事件,觀察者就會有所響應

事件

觀察者關注的事件就是鈴铛是否響,鈴铛一響說明嬰兒醒了.嬰兒一醒,媽媽就要去照顧,比如喂奶等.

注冊(訂閱)

媽媽在嬰兒身上挂一個鈴铛,就是注冊事件.

一旦該事件發生,觀察者就會有所響應.一旦嬰兒醒了,媽媽就會聽到鈴铛聲(收到通知)

通知

發生事件(嬰兒醒了)時,被觀察者(嬰兒)會主動通知觀察者(媽媽)

java 類圖

設計模式-觀察者模式

示例

接口

觀察者:

/**
 * 接口描述: 觀察者. <br />
 *
 * @author hanjun.hw
 * @since 2018/11/6
 */
public interface Observer {
    /**
     * 觀察者收到通知後,觀察者進行響應
     *
     * @param observable
     */
    void update(Observable observable);
}           

被觀察者:

public abstract class Observable {
    /**
     * 訂閱觀察者
     *
     * @param observer
     */
    public abstract void register(Observer observer);


    /**
     * 通知觀察者
     */
    protected abstract void notifyAllObservers();


    /**
     * 觀察者真正感興趣的事件
     */
    public void wakeUp() {
        bellRinging();
        notifyAllObservers();
    }


    /**
     * 觀察者觀察的事件
     */
    protected abstract void bellRinging();
}           

觀察者實作

/**
 * 類描述: 具體觀察者:媽媽. <br />
 *
 * @author hanjun.hw
 * @since 2018/11/6
 */
public class Mother implements Observer {
    @Override
    public void update(Observable observable) {
        System.out.println("嬰兒醒了,去照看 :" + observable);
    }
}           

被觀察者實作

/**
 * 類描述: 被觀察者:嬰兒. <br />
 *
 * @author hanjun.hw
 * @since 2018/11/6
 */
public class Baby extends Observable {
    private HashSet<Observer> observers = new HashSet<>();


    /**
     * 把鈴铛系在嬰兒身上
     * @param observer
     */
    @Override
    public void register(Observer observer) {
        observers.add(observer);
    }


    /**
     * 媽媽聽到鈴铛聲,做出響應
     */
    @Override
    protected void notifyAllObservers() {
        observers.forEach(o -> o.update(this));
    }


    @Override
    protected void bellRinging() {
        System.out.println("嬰兒睡醒了 ,鈴铛響了");
    }
}           

測試

public static void main(String[] args) {
        Baby concreteObservable = new Baby();
        Mother concreteObserver = new Mother();
        concreteObservable.register(concreteObserver);


        concreteObservable.wakeUp();
    }           

應用場景

Google eventbus

組成部分

事件(什麼類型的事件)---對應鈴铛響了

事件監聽器,即事件處理程式(響應)----對應媽媽

注冊事件監聽器(register);----對應往嬰兒身上挂鈴铛

觸發事件(trigger/post);---嬰兒醒了,搖動了鈴铛

執行個體

事件,可以是任何自定義對象

/**
 * Created by whuanghkl on 17/6/22.<br />
 * 自定義事件
 */
public class AccessLoggerEvent {

}           

事件監聽器

/**
 * Created by whuanghkl on 17/6/22.<br />
 * 事件監聽器
 */
@Component
public class AccessLoggerListener {
    @Resource
    private EventBus eventBus;
    /**
    * 訂閱
    */
    @PostConstruct
    public void init() {
        eventBus.register(this);
    }


    @Subscribe
    public void logEvent(AccessLoggerEvent event) {
        System.out.println("logEvent");
    }
}           

事件監聽器自己注冊到eventBus

在控制器中觸發事件

AccessLoggerEvent accessLoggerEvent = new AccessLoggerEvent();
        eventBus.post(accessLoggerEvent);           

嬰兒是什麼? 是上述代碼(eventBus.post)所在類

java swing 按鈕單擊/文本框回車

JButton cancelButton = new JButton("Cancel");
cancelButton.setActionCommand("Cancel");
cancelButton.addActionListener(new ActionListener() {//ActionListener 是觀察者
    @Override
    public void actionPerformed(ActionEvent e) {//觀察者的響應
        LoginDialog.this.dispose();
    }
});           

涉及的元素:

按鈕 -----對應嬰兒

按鈕增加事件 ,即調用 addActionListener() ---對應往嬰兒身上挂鈴铛

使用者點選按鈕;---嬰兒醒了,搖動了鈴铛

執行 addActionListener()

執行 addActionListener() 添加的ActionListener ---對應 媽媽聽到鈴聲之後去照看

媽媽是什麼? 是ActionListener 對象

react 資料的雙向綁定

參考:

https://juejin.im/post/59f2e9b16fb9a04529360146

mq 消息隊列訂閱

多路複用機制epoll 也使用了觀察者模式,

與之相對的select:整個socket集合會被周遊一次,比如集合裡面有1024個socket,即便隻有一個socket有資料可讀,也周遊所有的1024個socket,這個是觀察者模式的反例.

epoll的實作邏輯就是觀察者模式的思想:

協定資料包到達網卡并被排入socket的接收隊列。

睡眠在socket的睡眠隊列wait_entry被喚醒,wait_entry_sk的回調函數epoll_callback_sk被執行。

epoll_callback_sk将目前socket插入epoll的ready_list中。

這樣就不用周遊整個socket隊列了,而隻需要周遊ready_list

觀察者模式的作用

解耦,比如 Google eventbus

提高響應速度(相對于輪詢)

一般是輔助的容易變化的業務

觀察者模式和mq(釋出訂閱) 的差別

mq:削峰,被觀察者和觀察者不用感覺對方的存在;

觀察者模式中,觀察者和被觀察者可以感覺到對方的存在

思考

設計模式是經驗的總結,是一套被反複使用的、多數人知曉的、經過分類編目的、代碼設計經驗的總結;

觀察者模式其實在日常生活中也有展現,

比如銀行業務 有短信通知,如果我綁定了手機号(訂閱),那麼銀行卡有交易時(關注的事件發生),就會自動給我們發短信(響應)

既能把觀察者模式了解成為一種思想,也能了解為解耦的一種政策