概述
在程式碼中我們經常會有if…else…判斷,一個條件不滿足就進行下一個判斷,這種就類似於責任鏈模式,只不過責任鏈模式是通過物件來過濾。
場景
在物聯網行業中,一個裝置會以一定的頻率向伺服器推送資料,方便伺服器對機器進行一個資料採集和監控,這個資料的型別是多種多樣的。例如娃娃機來說:會有裝置狀態的資料、裝置定位的資料、裝置報警的資料等等各種資料。每一種型別的資料都由很多個欄位組成,例如裝置狀態資料包含:當前時間、機器號、機器狀態(上線、下線、離線),一般都是以二進位制的形式進行傳輸,為了方便就假設裝置以JSON的格式上報過來,我接收到資料再進行一個相應的處理。
普通的程式碼實現
首先能想到的就是利用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);
}
}
執行結果
但是這樣會有很多問題,如果還有其他型別的資料那麼又要增加判斷,這個條件判定的順序也是寫死的,非常不靈活,接下來用責任鏈模式進行優化
責任鏈實現
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);
}
}
- 如果有新的裝置訊息型別,只需要加一個新的事件處理類,其他程式碼不用變化,這樣符合開放封閉原則還有單一原則,也增加了程式的靈活性。
- 具體使用到哪個型別也不需要我們自己,交給程式執行時處理
- 使用Map集合的方式,直接從集合裡面根據特徵找到對應的處理器,跟其他部落格設定使用下一個處理者進行判斷的方法類似,如果鏈條比較長那麼使用下一個處理者方法不合適,需要從頭遍歷到尾部。
- 還可以控制請求順序,集合的話通過增加一個排序欄位
總結
責任鏈模式其實就是靈活的if..else..語句,將多個處理者連線成一條鏈。 接收到請求後, 它會 “詢問” 每個處理者是否能夠對其進行處理。 這樣所有處理者都有機會來處理請求
使用場景
- 當必須按順序執行多個處理者時,可以使用該模式
- 如果所需處理者及其順序必須在執行時進行改變, 可以使用責任鏈模式
- 當程式需要使用不同方式處理不同種類請求,而且請求型別和順序預先未知時,可以使用責任鏈模式