設計模式思維拓展與實戰:觀察者模式

今天我們聊的設計模式是觀察者模式,在很多開源的框架中都有它的影子,比如大名鼎鼎的

Spring,它的源碼中涉及到了很多listenerevent,同時 JDK 源碼也有涉及它的思想,比如 awt 包下面的窗口事件、按鈕事件等等,這都體現了觀察者模式的重要性,所以我們要掌握它的設計原理,並在實際應用中精通它,捅破這層窗戶紙,好了,廢話不多說,咱們還是看業務場景,我們在網上購買商品時,最後一步下訂單操作完成之後,系統會為我們生成訂單、扣款、發短信等等,其實無論有多少步操作,都是屬於同一個事件,就是下單事件,而這些一個個的步驟,我們可以把它們看成該事件的觀察者生成訂單觀察者、扣款觀察者和發短信觀察者),同時我們也可以把用戶下單的操作看成是事件源,事件源控制著觀察者的發佈與訂閱,當然也可以說成是事件源控制著觀察者的啟動與註冊
,老路子,我們還是直接看流程圖和代碼說明。

設計模式思維拓展與實戰:觀察者模式

咱們看一下上面的流程圖,三個觀察者(生成訂單觀察者、支付觀察者和發送短信觀察者)被註冊到了下單事件源中,然後事件源發佈了下單的事件,並把該事件以同步或者異步的方式通知到每一個觀察者,大家只看流程圖可能不容易理解,先彆著急,我們接下來看代碼說明:

首先我們先創建一個下單數據實體類OrderData,裡面有兩個屬性,消費金額和購買商品數量: 注: @Data @AllArgsConstructor 這兩個註解是 lombok 包裡面的,引入這個包就不用再寫 set、get 方法和構造函數了。

<code>package com.observer.resource;
import  java.math.BigDecimal;

import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * 訂單數據
 */
@Data
@AllArgsConstructor
public class OrderData {

    /**
     * 消費金額
     */
    private BigDecimal money;
    /**
     * 購買商品個數
     */
    private int buyCount;
}/<code>

接下來創建一個PlaceOrderEvent類作為下單事件,裡面有一個成員變量,也就是剛剛創建的訂單數據實體類:

<code>package com.observer.event;

import com.observer.resource.OrderData;
import lombok.AllArgsConstructor;
import lombok.Data;

/**
 * 下單事件
 */
@Data
@AllArgsConstructor
public class PlaceOrderEvent{

    private OrderData orderData;

}/<code>

然後我們繼續創建下單的觀察者接口PlaceOrderObserver,裡面有一個方法

actionPlaceOrder,用於執行下單的具體操作,並把PlaceOrderEvent作為參數傳進來:

<code>package com.observer.service;
import com.observer.event.PlaceOrderEvent;

/**
 * 下單觀察者
 */
public interface PlaceOrderObserver{
    /**
     * 觸發下單操作
     * @param e
     */
    void actionPlaceOrder(PlaceOrderEvent e);
}/<code>

接著我們要創建三個類用於模擬生成訂單、支付和發送短信業務,並且同時實現PlaceOrderObserver接口,代表它們都是這個事件的觀察者: 注: @Slf4j 註解也是 lombok 裡面的

GenerateOrdersObserver 生產訂單的觀察者類:

<code>package com.observer.service.impl;

import com.observer.event.PlaceOrderEvent;
import com.observer.resource.OrderData;
import com.observer.service.PlaceOrderObserver;
import lombok.extern.slf4j.Slf4j;

/**
 * 生成訂單觀察者類
 */
@Slf4j
public class GenerateOrdersObserver implements PlaceOrderObserver {


    @Override
    public void actionPlaceOrder(PlaceOrderEvent e) {
        this.addOrder(e.getOrderData());
    }

    /**
     * 生成訂單
     * @param orderData
     */
    private void addOrder(OrderData orderData){
        log.info("生成訂單成功,訂單金額:{},購買商品數:{}",
                orderData.getMoney(),orderData.getBuyCount());
    }
}/<code>

PayObserver 支付觀察者類

<code>package com.observer.service.impl;

import com.observer.event.PlaceOrderEvent;
import com.observer.resource.OrderData;
import com.observer.service.PlaceOrderObserver;
import lombok.extern.slf4j.Slf4j;

/**
 * 支付觀察者類
 */
@Slf4j
public class PayObserver implements PlaceOrderObserver {

    @Override
    public void actionPlaceOrder(PlaceOrderEvent e) {
        this.payMoney(e.getOrderData());
    }

    /**
     * 支付
     * @param orderData
     */
    private void payMoney(OrderData orderData){
        log.info("支付成功{}元",orderData.getMoney());
    }

}/<code>

SendMsgObserver 發送短信觀察者類

<code>package com.observer.service.impl;

import com.observer.event.PlaceOrderEvent;
import com.observer.resource.OrderData;
import com.observer.service.PlaceOrderObserver;
import lombok.extern.slf4j.Slf4j;

/**
 * 發送短信觀察者類
 */
@Slf4j
public class SendMsgObserver implements PlaceOrderObserver {

    @Override
    public void actionPlaceOrder(PlaceOrderEvent e) {
        this.sendMsg(e.getOrderData());
    }

    /**
     * 發送短信
     * @param orderData
     */
    private void sendMsg(OrderData orderData){
        log.info("發送短信成功,短信內容:購買商品數為{},總共消費了{}元",
                orderData.getBuyCount(),orderData.getMoney());
    }
}/<code> 

三個觀察者類創建後,接著我們要創建一個事件源接口類TriggerSource用於發佈和訂閱觀察者,該接口有兩個方法,第一個方法startUpObserver用來啟動觀察者或者說是發佈這個事件,第二個方法registObserver是觀察者註冊,只有先註冊了觀察者才可以被髮布或啟動

<code>package com.observer.triggerObserver;

import com.observer.event.PlaceOrderEvent;
import com.observer.service.PlaceOrderObserver;

/**
 * 觸發觀察者啟動頂層類
 */
public interface TriggerSource {

    /**
     * 啟動觀察者(發佈功能)
     * @param event
     */
    void startUpObserver(PlaceOrderEvent event);

    /**
     * 註冊觀察者(訂閱功能)
     * @param observer
     */
    void registObserver(PlaceOrderObserver observer);

}/<code>

然後我們繼續創建一個事件源接口的實現類PlaceOrderTriggerSource,用於專門發佈事件和訂閱觀察者

<code>package com.observer.triggerObserver;
import com.google.common.collect.Lists;
import com.observer.event.PlaceOrderEvent;
import com.observer.service.PlaceOrderObserver;
import com.observer.event.Event;

import java.util.List;

/**
 * 觀察下單啟動類
 */
public class PlaceOrderTriggerSource implements TriggerSource{

    private List observerList = Lists.newArrayList();//被觀察者列表

    @Override
    public void startUpObserver(PlaceOrderEvent event) {
        for(PlaceOrderObserver observer : observerList){
            observer.actionPlaceOrder(event);//循環執行各個觀察者方法
        }
    }

    @Override
    public void registObserver(PlaceOrderObserver observer) {
        observerList.add(observer);//添加到觀察者容器中
    }

}/<code>

簡單說明一下PlaceOrderTriggerSource類,因為要全部執行和該事件相關的觀察者方法,所以startUpObserver函數採用 for 循環的方式迭代執行,這也是觀察者模式比較重要的一點,

observerList是存放觀察者的容器,第二個方法registObserver負責把觸發該事件的觀察者添加到容器中。

最後我們寫一個測試類,來進行測試:

<code>package com.test;

import com.observer.event.PlaceOrderEvent;
import com.observer.resource.OrderData;
import com.observer.service.impl.GenerateOrdersObserver;
import com.observer.service.impl.PayObserver;
import com.observer.service.impl.SendMsgObserver;
import com.observer.triggerObserver.PlaceOrderTriggerSource;
import com.observer.triggerObserver.TriggerSource;
import org.junit.Test;

import java.math.BigDecimal;

public class TestObserver {

    @Test
    public void testObserver(){

        TriggerSource placeOrderTriggerObserver = new PlaceOrderTriggerSource();
        //把觀察者對象添加到啟動觀察者實例中,相當於訂閱
        placeOrderTriggerObserver.registObserver(new GenerateOrdersObserver());
        placeOrderTriggerObserver.registObserver(new PayObserver());
        placeOrderTriggerObserver.registObserver(new SendMsgObserver());
        //實例化下單事件類,並且設置訂單數據
        PlaceOrderEvent event = new PlaceOrderEvent(new OrderData(BigDecimal.valueOf(1000),20));
        //把下單事件傳給所有的觀察者,然後執行他們自己的業務邏輯,相當於發佈
        placeOrderTriggerObserver.startUpObserver(event);

    }
}/<code>

打完收工,我們來看一下執行結果:

設計模式思維拓展與實戰:觀察者模式

ok,執行沒有問題,假如下單操作中又增加了一項積分的業務,我們只需要實現一個積分的觀察者同時把它註冊到下單的事件源中就可以了,同樣的,如果不需要短信業務類,那麼只需要取消掉短信觀察者的註冊就可以了,無論是增加還是取消業務,都能減少各模塊的的耦合性,觀察者模式我們今天就聊到這裡,歡迎大家留言哦,如果不錯的請給一個好評,之後還有更多精彩的內容分享給大家,我在 gitchat 還有一篇文章是專門講解策略模式實戰的,在 spring 框架下如何使用策略模式,同時利用註解和枚舉的方式把網關思想融入了進去,從而實現代碼零侵入的業務擴展功能,非常實用哦,因為我在實際工作中經常使用它,歡迎大家來訂閱哦。


分享到:


相關文章: