你還不知道責任鏈模式的使用場景嗎?

炒燜煎糖板栗發表於2022-04-23

概述

在程式碼中我們經常會有if…else…判斷,一個條件不滿足就進行下一個判斷,這種就類似於責任鏈模式,只不過責任鏈模式是通過物件來過濾。

場景

在物聯網行業中,一個裝置會以一定的頻率向伺服器推送資料,方便伺服器對機器進行一個資料採集和監控,這個資料的型別是多種多樣的。例如娃娃機來說:會有裝置狀態的資料、裝置定位的資料、裝置報警的資料等等各種資料。每一種型別的資料都由很多個欄位組成,例如裝置狀態資料包含:當前時間、機器號、機器狀態(上線、下線、離線),一般都是以二進位制的形式進行傳輸,為了方便就假設裝置以JSON的格式上報過來,我接收到資料再進行一個相應的處理。

image-20220421214953234

普通的程式碼實現

首先能想到的就是利用if…else…,如果是裝置報警的資料我就使用裝置報警處理器處理,超簡單的,開始編碼~

1、實體類

DeviceAlarm類

package com.ylc.model;

import lombok.Data;

/**
 * 裝置狀態實體類
 * @author yanglingcong
 * @date 2022/4/20 21:08
 */
@Data
public class DeviceStatus {


    /**
     * 更新時間
     */
    private  long updateTime;

    /**
     * 狀態
     * 0 未準備
     * 1 準備
     * 2 正常執行
     * 3 異常
     */
    private Integer state;

    /**
     * 資料型別
     */
    private String type;

}

DeviceGps類

/**
 * 裝置GPS實體類
 *
 * @author yanglingcong
 * @date 2022/4/20 21:08
 */
@Data
public class DeviceGps {

    /**
     * 經度
     */
    private Float longitude;

    /**
     * 緯度
     */
    private Float latitude;


    /**
     * 水平分量精度因子:
     */
    private Float hdop;

}

DeviceAlarm類

package com.ylc.model;

import lombok.Data;

/**
 * 裝置報警實體類
 *
 * @author yanglingcong
 * @date 2022/4/20 21:08
 */
@Data
public class DeviceAlarm {

    /**
     * 報警訊息
     */
    private String alarmMsg;

    /**
     * 報警狀態
     */
    private Integer alarmStatus;
}

2、訊息的列舉型別

package com.ylc.model;

import lombok.Getter;

/**
 * 裝置訊息列舉型別
 * @author yanglingcong
 * @date 2022/4/20 21:08
 */
@Getter
public enum eventEnum {

    STATUS("10001"),

    ALARM("10002"),

    GPS("10003");

    private String code;

    eventEnum(String code){
        this.code=code;
    }
}

3、事件介面

/**
 * 處理器介面
 * @author yanglingcong
 * @date 2022/4/19 22:59
 */
public interface AbstractHandler {

    String getEventType();

    void handle(JSONObject jsonObject);

}

3、事件處理

DeviceAlarmEvent

/**
 * 裝置報警事件
 * @author yanglingcong
 * @date 2022/4/19 22:59
 */
@Slf4j
@Component
public class DeviceAlarmEvent   implements  AbstractHandler{

    @Override
    public String getEventType() {
        return eventEnum.ALARM.getCode();
    }

    @Override
    public void handle(JSONObject jsonObject) {
        DeviceAlarm deviceAlarm = jsonObject.toJavaObject(DeviceAlarm.class);
        log.info("裝置報警事件被處理");
        //業務處理.....
    }
}

DeviceGpsEvent

/**
 * 裝置定位事件
 * @author yanglingcong
 * @date 2022/4/19 22:59
 */
@Component
@Slf4j
public class DeviceGpsEvent implements AbstractHandler{

    @Override
    public String getEventType() {
        return eventEnum.GPS.getCode();
    }

    @Override
    public void handle(JSONObject jsonObject) {
        DeviceGps deviceGps = jsonObject.toJavaObject(DeviceGps.class);
        //業務處理.....
        log.info("裝置定位事件被處理");
    }
}

DeviceStatusEvent

/**
 * 裝置狀態事件
 * @author yanglingcong
 * @date 2022/4/19 22:59
 */
@Slf4j
@Component
public class DeviceStatusEvent implements  AbstractHandler{

    @Override
    public String getEventType() {
        return eventEnum.STATUS.getCode();
    }

    @Override
    public   void  handle(JSONObject jsonObject){
        DeviceStatus deviceStatus = jsonObject.toJavaObject(DeviceStatus.class);
        //業務處理.....
        log.info("裝置狀態事件被處理");
    }
}

4、訊息分發中心

package com.ylc.handle;

import com.alibaba.fastjson.JSONObject;
import com.ylc.model.eventEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

/**
 * 資料事件處理類
 * @author yanglingcong
 */
@Slf4j
@Component
public class PushEvent {

    /**
     * 資料分發到對應的事件處理
     */
    public void dispatch(JSONObject jsonObject){
        String code = (String) jsonObject.get("type");
        //如果是裝置狀態資料
        if(code.equals(eventEnum.STATUS.getCode())){
            log.info("開始處理裝置狀態資料");
            DeviceStatusEvent statusEvent=new DeviceStatusEvent();
            statusEvent.handle(jsonObject);
        }
        //如果是裝置定位資料
        else if(code.equals(eventEnum.GPS.getCode())){
            log.info("開始處理裝置定位資料");
            DeviceGpsEvent deviceGpsEvent=new DeviceGpsEvent();
            deviceGpsEvent.handle(jsonObject);
        }
        //如果是裝置報警資料
        else if(code.equals(eventEnum.ALARM.getCode())){
            log.info("開始處理裝置定位資料");
            DeviceStatusEvent statusEvent=new DeviceStatusEvent();
            statusEvent.handle(jsonObject);
        }
    }

}

6、測試

@Slf4j
public class MessageHandleTest {

    @Test
    public void  testDeviceStatus(){
        DeviceStatus deviceStatus=new DeviceStatus();
        deviceStatus.setType(eventEnum.STATUS.getCode());
        deviceStatus.setUpdateTime(1653532367);
        deviceStatus.setState(1);
        JSONObject jsonObject= JSON.parseObject(JSONObject.toJSONString(deviceStatus));
        PushEvent pushEvent=new PushEvent();
        log.info("開始分發訊息:{}",deviceStatus.toString());
        pushEvent.dispatch(jsonObject);
    }

}

執行結果

image-20220421221949165

但是這樣會有很多問題,如果還有其他型別的資料那麼又要增加判斷,這個條件判定的順序也是寫死的,非常不靈活,接下來用責任鏈模式進行優化

責任鏈實現

1、實體類 略

2、事件處理 略

3、訊息分發中心

package com.ylc.handle;

import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


/**
 * 資料事件處理類
 * @author yanglingcong
 */
@Slf4j
@Component
public class PushEvent implements ApplicationContextAware {

     /**
     * 實現類集合
     * */
    private Map<String, List<AbstractHandler>> routerMap;

    @Autowired
    ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.routerMap =applicationContext.getBeansOfType(AbstractHandler.class).values()
                .stream().collect(Collectors.groupingBy(AbstractHandler::getEventType));
    }

    /**
     * 資料分發到對應的事件處理
     */
    public void dispatch(JSONObject jsonObject){
        String code = (String) jsonObject.get("type");
        List<AbstractHandler> pushEventHandlers= this.routerMap.get(code);
        for (AbstractHandler pushEventHandler : pushEventHandlers) {
            log.info("開始處理{}事件",pushEventHandler.getEventType());
            pushEventHandler.handle(jsonObject);
        }
    }
}

4、測試

package com.ylc;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ylc.handle.AbstractHandler;
import com.ylc.handle.PushEvent;
import com.ylc.model.DeviceStatus;
import com.ylc.model.eventEnum;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class MessageHandleTest {

    @Autowired
    PushEvent pushEvent;

    @Test
    public void  testDeviceStatus(){
        DeviceStatus deviceStatus=new DeviceStatus();
        deviceStatus.setType(eventEnum.STATUS.getCode());
        deviceStatus.setUpdateTime(1653532367);
        deviceStatus.setState(1);
        JSONObject jsonObject= JSON.parseObject(JSONObject.toJSONString(deviceStatus));
        log.info("開始分發訊息:{}",deviceStatus.toString());
        pushEvent.dispatch(jsonObject);
    }

}

image-20220423140135494

  • 如果有新的裝置訊息型別,只需要加一個新的事件處理類,其他程式碼不用變化,這樣符合開放封閉原則還有單一原則,也增加了程式的靈活性。
  • 具體使用到哪個型別也不需要我們自己,交給程式執行時處理
  • 使用Map集合的方式,直接從集合裡面根據特徵找到對應的處理器,跟其他部落格設定使用下一個處理者進行判斷的方法類似,如果鏈條比較長那麼使用下一個處理者方法不合適,需要從頭遍歷到尾部。
  • 還可以控制請求順序,集合的話通過增加一個排序欄位

總結

責任鏈模式其實就是靈活的if..else..語句,將多個處理者連線成一條鏈。 接收到請求後, 它會 “詢問” 每個處理者是否能夠對其進行處理。 這樣所有處理者都有機會來處理請求

使用場景

  • 當必須按順序執行多個處理者時,可以使用該模式
  • 如果所需處理者及其順序必須在執行時進行改變, 可以使用責任鏈模式
  • 當程式需要使用不同方式處理不同種類請求,而且請求型別和順序預先未知時,可以使用責任鏈模式

相關文章