責任連鎖モード - パラメータの検証

ヒント: 記事を作成した後、目次を自動的に生成できます。生成方法は、右側のヘルプドキュメントを参照してください。


序文

デザインパターンを仕事に適用するための責任連鎖パターン

1. 責任連鎖モデルの概念

  • Chain of Responsibility パターンは、リクエストの送信者と受信者を切り離し、複数の処理オブジェクトで責任の連鎖を形成し、処理できるリクエストが見つかるまでこの責任の連鎖に沿って処理オブジェクトを順番に呼び出す動作設計パターンです。オブジェクトのリクエスト。
  • Chain of Responsibility パターンでは、いずれかのハンドラーがリクエストを処理できるようになるか、チェーンの終わりに到達するまで、リクエストは各ハンドラーに順番に渡されます。各処理オブジェクトは、処理するリクエストにのみ焦点を当てる必要があるため、システムのビジネス ロジックを効果的に分離し、コードの柔軟性とスケーラビリティを向上させることができます。

2. 責任連鎖モデルの主な適用シナリオ

  • 複数のビジネス処理オブジェクトの動的な組み合わせと実行が必要なシナリオ
  • さまざまなタイプ、レベル、優先度、またはソースのリクエストの分類と処理が必要なシナリオ
  • 同じリクエストを処理できるオブジェクトは複数ありますが、どのオブジェクトがリクエストを処理するかは実行時にのみ決定できます。

3. 責任連鎖モデルの利点

  • リクエストの送信者と受信者を効果的に分離できる
  • オブジェクトは動的に追加、削除、または再配置できます。
  • さまざまなタイプ、レベル、優先度、ソースの要求に合わせて処理オブジェクトを柔軟に設定できます。

4. 責任連鎖モデルの欠点

  • 責任の連鎖が長すぎると、リクエストの配信時間が長くなり、システムのパフォーマンスが低下する可能性があります。
  • 責任の連鎖には、どの処理オブジェクトがリクエストを処理するかを制御するための明確な指示がなく、リクエストは責任の連鎖の最後に渡されるため、一定の不確実性が生じます。

5. シナリオケース: パラメータの検証

SpringBoot使用责任链模式校验Map<String,Object>的每一个v是否符合k类型,k的取值有1(字符串)2(布尔类型)3(浮点类型)4(时间类型)...

1.UML図

ここに画像の説明を挿入

2. コードの実装

2.1. リクエストボディの定義

@Data
@NoArgsConstructor
public class ParamRequest {
    
    
    /**
     * 数据类型
     */
    private Integer dataType;

    /**
     * 值
     */
    private Object value;

    // 其它限制条件
    /**
     * 是否必填
     */
    private boolean isRequired;
    /**
     * 单选
     */
    private boolean isSelected;
    /**
     * 多选
     */
    private boolean areSelected;
    /**
     * 最小值
     */
    private String minValue;
    /**
     * 最大值
     */
    private String maxValue;
    /**
     * 备注
     */
    private String remark;

    public ParamRequest(Integer dataType, Object value) {
    
    
        this.dataType = dataType;
        this.value = value;
    }

    public ParamRequest(Integer dataType, Object value, boolean isRequired) {
    
    
        this.dataType = dataType;
        this.value = value;
        this.isRequired = isRequired;
    }
}

2.2. レスポンスボディの定義

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

import java.io.Serializable;

@ApiModel("响应")
public class Result<T> implements Serializable {
    
    
    private static final long serialVersionUID = 1L;
    @ApiModelProperty("编码:0表示成功,其他值表示失败")
    private int code = 0;
    @ApiModelProperty("消息内容")
    private String msg = "success";
    @ApiModelProperty("响应数据")
    private T data;

    public Result() {
    
    
    }

    public Result<T> ok(T data) {
    
    
        this.setData(data);
        return this;
    }

    public Result<T> okMsg(String msg) {
    
    
        this.code = 0;
        this.setMsg(msg);
        return this;
    }

    public boolean success() {
    
    
        return this.code == 0;
    }

    public Result<T> error() {
    
    
        this.code = 500;
        this.msg = "未知错误";
        return this;
    }

    public Result<T> error(int code) {
    
    
        this.code = code;
        this.msg = "未知错误";
        return this;
    }

    public Result<T> error(int code, String msg) {
    
    
        this.code = code;
        this.msg = msg;
        return this;
    }

    public Result<T> error(String msg) {
    
    
        this.code = 500;
        this.msg = msg;
        return this;
    }

    public Result<T> error(int code, T data) {
    
    
        this.code = code;
        this.data = data;
        return this;
    }

    public int getCode() {
    
    
        return this.code;
    }

    public void setCode(int code) {
    
    
        this.code = code;
    }

    public String getMsg() {
    
    
        return this.msg;
    }

    public Result<T> setMsg(String msg) {
    
    
        this.msg = msg;
        return this;
    }

    public T getData() {
    
    
        return this.data;
    }

    public void setData(T data) {
    
    
        this.data = data;
    }

    public String toString() {
    
    
        return "{\"code\":" + this.code + ",\"msg\":\"" + this.msg + '"' + ",\"data\":" + this.data + "}";
    }
}

2.3. データ型列挙定義

import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @author mxf
 * @version 1.0
 * @description: 数据类型枚举
 * @date 2023/5/23
 */
@Getter
@AllArgsConstructor
public enum DataTypeEnum {
    
    

    /**
     * 布尔类型
     */
    BOOLEAN(1, Boolean.class),
    /**
     * 日期类型
     */
    DATE(2, Date.class),
    /**
     * 浮点类型
     */
    FLOAT(3, Float.class),
    /**
     * 字符串类型
     */
    STRING(4, String.class),
    /**
     * 整值类型
     */
    INTEGER(5, Integer.class);
    final static Set<Integer> DATA_TYPE_ENUM_CODE_SET;
    final static List<DataTypeEnum> DATA_TYPE_ENUM_LIST;

    static {
    
    
        DATA_TYPE_ENUM_CODE_SET = Arrays.stream(DataTypeEnum.values())
                .map(DataTypeEnum::getCode)
                .collect(Collectors.toSet());

        DATA_TYPE_ENUM_LIST = Arrays.stream(DataTypeEnum.values()).collect(Collectors.toList());
    }

    /**
     * code
     */
    private final Integer code;
    /**
     * 参数类型
     * /
    private final Class<?> clazz;

    public static boolean exitByCode(Integer code) {
        return DATA_TYPE_ENUM_CODE_SET.contains(code);
    }

    public static Class<?> getClazzByCode(Integer code) {
        return DATA_TYPE_ENUM_LIST.stream().filter(dte -> dte.getCode().equals(code)).map(DataTypeEnum::getClazz).findFirst().orElse(null);
    }
}

2.4. データ検証プロセッサクラスの作成

斜体スタイルはデータ型を検証するために使用されます

import com.alibaba.fastjson.JSON;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;

/**
 * @author 28382
 */
public abstract class DataValidHandler<T> {
    
    

    protected DataValidHandler next;


    /**
     * 用于对下一个处理器的引用
     */
    public void setNext(DataValidHandler next) {
    
    
        this.next = next;
    }

    /**
     * 其它规则校验
     */
    public Result<Void> ruleVerification(String key, ParamRequest paramRequest) {
    
    
        // 默认实现
        return new Result<>();
    }

    private Class<?> getClazz() {
    
    
        // 使用反射获取 DataValidHandler 类中声明的泛型类型参数
        Type parameterizedType = this.getClass().getGenericSuperclass();
        return (Class<?>) ((ParameterizedType) parameterizedType).getActualTypeArguments()[0];
    }

    /**
     * 处理
     */
    public final Result<Void> handle(Map<String, ParamRequest> paramMap) {
    
    
        Iterator<Map.Entry<String, ParamRequest>> iterator = paramMap.entrySet().iterator();
        while (iterator.hasNext()) {
    
    
            Map.Entry<String, ParamRequest> entry = iterator.next();
            ParamRequest paramRequest = entry.getValue();
            if (Objects.isNull(paramRequest) || (getClazz() != DataTypeEnum.getClazzByCode(paramRequest.getDataType()))) {
    
    
                continue;
            }
            if (paramRequest.getValue() != null && !getClazz().isInstance(paramRequest.getValue())) {
    
    
                paramMap.clear();
                next = null;
                return new Result<Void>().error("Value of " + entry.getKey() + " is invalid: " + paramRequest.getValue());
            }
            Result<Void> ruleVerificationResult = ruleVerification(entry.getKey(), paramRequest);
            if (ruleVerificationResult.getCode() != 0) {
    
    
                paramMap.clear();
                next = null;
                return ruleVerificationResult;
            }
            iterator.remove();
        }

        return nextHandle(paramMap);
    }

    /**
     * @param paramMap 参数列表
     * @return com.mxf.code.chain_params.Result<java.lang.Void>
     * @author mxf
     * @description 下一个处理器
     * @createTime 2023/5/24 10:46
     * @paramType [java.util.Map<java.lang.String, com.mxf.code.chain_params.ParamRequest>]
     */
    public final Result<Void> nextHandle(Map<String, ParamRequest> paramMap) {
    
    
        Result<Void> handleResult = new Result<>();
        if (!Objects.isNull(next)) {
    
    
            handleResult = next.handle(paramMap);
        } else {
    
    
            if (!CollectionUtils.isEmpty(paramMap)) {
    
    
                return new Result<Void>().error("存在其它类型参数未被校验:" + JSON.toJSONString(paramMap));
            }
        }
        return handleResult;
    }
}

2.5. ブール値データ検証プロセッサの作成

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Objects;

/**
 * @author 28382
 */
@Component
@Order(0)
public class BooleanValidHandler extends DataValidHandler<Boolean> {
    
    
    @Override
    public Result<Void> ruleVerification(String key, ParamRequest paramRequest) {
    
    
        // 判断是否必填
        boolean isRequired = paramRequest.isRequired();
        if (isRequired && Objects.isNull(paramRequest.getValue())) {
    
    
            return new Result<Void>().error("Value of " + key + " is required");
        }
        return new Result<>();
    }
}

2.6. 日付データ検証プロセッサの作成

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @author 28382
 */
@Component
@Order(1)
public class DateValidHandler extends DataValidHandler<Date> {
    
    
	// 可重写ruleVerification()
}

2.7. 整数データ検証プロセッサの作成

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @author 28382
 */
@Component
@Order(2)
public class IntegerValidHandler extends DataValidHandler<Integer> {
    
    

}

2.8. 文字列データ検証プロセッサの作成

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @author 28382
 */
@Component
@Order(3)
public class StringValidHandler extends DataValidHandler<String> {
    
    

}

2.9. Float データ検証プロセッサの作成

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @author 28382
 */
@Component
@Order(4)
public class FloatValidHandler extends DataValidHandler<Float> {
    
    

}

2.10. プロセッサーチェーンの作成

このクラスは、SpringBoot によって提供される @Autowired アノテーションを使用して、DataValidHandler インターフェイスを実装するすべての Bean を注入し、Order アノテーションで指定された順序でプロセッサ チェーンをソートし、最後にすべてのプロセッサを直列に接続します。同時に、このクラスはデータ処理を開始するための handle() メソッドも提供します。

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.Comparator;
import java.util.List;
import java.util.Map;

/**
 * @author 28382
 */
@Service
public class DataValidHandlerChain {
    
    

    // 自动注入
    @Autowired
    private List<DataValidHandler> handlers;

    /**
     * 创建数据验证责任链
     */
    @PostConstruct
    public void init() {
    
    
        handlers.sort(Comparator.comparingInt(h -> getOrder(h.getClass())));
        for (int i = 0; i < handlers.size() - 1; i++) {
    
    
            handlers.get(i).setNext(handlers.get(i + 1));
        }
    }

    private int getOrder(Class<?> clazz) {
    
    
        Order order = clazz.getDeclaredAnnotation(Order.class);
        return (order != null) ? order.value() : Ordered.LOWEST_PRECEDENCE;
    }

    /**
     * 开始处理数据
     */
    public Result<Void> handle(Map<String, ParamRequest> data) {
    
    
        return handlers.get(0).handle(data);
    }
}

3. 単体テスト

3.1. 型の不一致

import com.mxf.code.chain_params.DataTypeEnum;
import com.mxf.code.chain_params.DataValidHandlerChain;
import com.mxf.code.chain_params.ParamRequest;
import com.mxf.code.chain_params.Result;
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.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.HashMap;
import java.util.Map;

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

    @Autowired
    private DataValidHandlerChain handlerChain;

    @Test
    public void test01() {
    
    
        Map<String, ParamRequest> data = new HashMap<>();
        data.put("name", new ParamRequest(DataTypeEnum.STRING.getCode(), 1));
        Result<Void> handle = handlerChain.handle(data);
        log.info("handle = {}", handle);
    }
}

ここに画像の説明を挿入

3.2. カスタム型の検証

ブール型を空にすることはできません

@Test
public void test02() {
    
    
    Map<String, ParamRequest> data = new HashMap<>();
    data.put("male", new ParamRequest(DataTypeEnum.BOOLEAN.getCode(), null, true));
    Result<Void> handle = handlerChain.handle(data);
    log.info("handle = {}", handle);
}

ここに画像の説明を挿入

3.3. 他にも不明なデータ型があります

@Test
public void test03() {
    
    
     Map<String, ParamRequest> data = new HashMap<>();
     data.put("subordinate",new ParamRequest(null,""));
     Result<Void> handle = handlerChain.handle(data);
     log.info("handle = {}", handle);
 }

ここに画像の説明を挿入

要約する

このメソッドは、値が期待されるデータ型とカスタム検証ルールに準拠しているかどうかを判断するために、動的パラメーターの受け渡しのシーンに適用できます。

おすすめ

転載: blog.csdn.net/weixin_44063083/article/details/130852557