Chain of Responsibility Mode - Parameter Validation

Tip: After the article is written, the table of contents can be automatically generated. How to generate it can refer to the help document on the right


foreword

Chain of Responsibility Pattern for Application of Design Patterns in Work

1. Concept of Chain of Responsibility Model

  • The Chain of Responsibility pattern is a behavioral design pattern that decouples the sender and receiver of the request, forms a chain of responsibility with multiple processing objects, and calls the processing objects sequentially along this chain of responsibility until it finds a request that can handle the request of objects.
  • In the Chain of Responsibility pattern, requests are passed to each handler in turn until one of the handlers is able to handle the request or the end of the chain is reached. Since each processing object only needs to focus on the requests it handles, it can effectively separate the business logic of the system and increase the flexibility and scalability of the code.

2. Main Application Scenarios of the Chain of Responsibility Model

  • Scenarios that require dynamic combination and execution of multiple business processing objects
  • Scenarios that require classification and processing of requests of different types, levels, priorities, or sources
  • There are multiple objects that can handle the same request, but which object handles the request can only be determined at runtime

3. Advantages of the Chain of Responsibility Model

  • Can effectively decouple request sender and receiver
  • Objects can be dynamically added, removed, or rearranged
  • You can flexibly set processing objects to meet requests of different types, levels, priorities or sources

4. Disadvantages of the Chain of Responsibility Model

  • If the chain of responsibility is too long, it may lead to a longer request delivery time and reduce system performance
  • There is no clear instruction in the chain of responsibility to control which processing object will process the request, and the request will be passed to the end of the chain of responsibility, bringing certain uncertainty

5. Scenario case: parameter verification

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

1. UML diagram

insert image description here

2. Code implementation

2.1. Request body definition

@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. Response body definition

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. Data type enumeration definition

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. Create a data validation processor class

Italic styles are used to validate data types

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. Create a Boolean data validation processor

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. Create a Date data validation processor

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. Create Integer data validation processor

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

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

}

2.8. Create a String data validation processor

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

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

}

2.9. Create a Float data validation processor

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

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

}

2.10. Create a processor chain

This class uses the @Autowired annotation provided by SpringBoot to inject all beans that implement the DataValidHandler interface, sorts the processor chain in the order specified by the Order annotation, and finally connects all processors in series. At the same time, this class also provides a handle() method to start data processing.

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. Unit testing

3.1. Type mismatch

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);
    }
}

insert image description here

3.2. Custom type validation

Boolean type cannot be empty

@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);
}

insert image description here

3.3. There are other unknown data types

@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);
 }

insert image description here

Summarize

This method can be applied to the scene of dynamic parameter passing, to judge whether the value conforms to the expected data type and custom verification rules

Guess you like

Origin blog.csdn.net/weixin_44063083/article/details/130852557