当前位置:网站首页>Advanced usage of the responsibility chain pattern

Advanced usage of the responsibility chain pattern

2022-07-19 02:56:00 The sound of the waves is still loud

 Insert picture description here

One 、 The opening

What knowledge can you learn by reading this article ?

  • Combined with specific cases , appreciate The chain of responsibility model The charm of .
  • The realization of responsibility chain model Process planning Dynamic expansion .
  • Use Sping@Resource Note the injection of Operation .
  • Use A recursive algorithm Set up responsibility link .

Two 、 brief introduction

The chain of responsibility model , In short , Is to assemble multiple operations into a link for processing . The request is passed on the link , Every node on the link is a processor , Each processor can process requests , Or pass it to the next processor on the link for processing .
 The chain of responsibility model - Link delivery



3、 ... and 、 Application scenarios

The application scenario of the responsibility chain pattern , In practice , There are usually two application scenarios .

  1. The operation needs to go through a series of checks , After passing the verification, some operations are performed .
  2. workflow . Enterprises usually develop many workflow , Deal with tasks level by level .

Let's learn about the responsibility chain model through two cases .


Case a : Create a multi-level verification scenario

Take creating goods as an example , Suppose that the logic of product creation is divided into the following three steps :① Create merchandise 、② Verify the product parameters 、③ Save item .

The first ② Step verification of goods is divided into verification of various situations , Required field verification 、 Specification verification 、 Price check 、 Inventory verification, etc . These inspection logics are like an assembly line , To create a product , Must pass these checks . As shown in the flow chart below :

 Create a bad taste of goods

The pseudocode is as follows :

Create product steps , It needs to go through a series of parameter verification , If parameter validation fails , Directly return the result of failure ; After all parameters are verified , Finally save the product information .
 Pseudo code

The above code seems to be ok , It's very neat , And the code logic is very clear .(PS: I didn't list all the verification codes in one method , That's more comparable , But I think Abstract and separate functions with a single responsibility It should be the most basic specification for every programmer !)

But as business needs continue to stack , There are more and more related verification logic , New functions make the code more and more Overstaffed , Poor maintainability . What's worse is , These calibration components Do not reuse , When you have other requirements and need to use some verification , You have become Ctrl+C , Ctrl+V The programmer , The maintenance cost of the system is also getting higher . As shown in the figure below :
 Business logic stack
Pseudo code is the same as above , I won't repeat it here .

One day at last , You can't stand it , decision Refactor this code .

Use the responsibility chain model to optimize : Each verification step of creating an item can be used as a separate processor , Separate into a single class , Easy Reuse . These processors form a call chaining , Requests are passed on the processor chain , If the verification conditions fail , Then the processor will no longer pass down requests , Return error message directly ; If all processors pass the test , Then execute the steps of saving goods .

 Insert picture description here



Case 1 actual combat : The responsibility chain mode realizes the creation of commodity verification


UML chart : The hills are small

 Insert picture description here

AbstractCheckHandler Represents the processor abstract class , Responsible for abstracting processor behavior . Its have 3 A subclass , Namely :

  1. NullValueCheckHandler: Null check processor
  2. PriceCheckHandler: Price verification processing
  3. StockCheckHandler: Inventory verification processor

AbstractCheckHandler In an abstract class , handle() Defines the abstract method of the processor , Its subclasses need rewrite handle() Method to implement special processor verification logic ;

protected ProductCheckHandlerConfig config Is the dynamic configuration class of the processor , Use protected Statement , Each subclass processor holds the object . This object is used to declare Current processor 、 And the current processor Next processor nextHandler, In addition, you can configure some special properties , for instance Interface degradation To configure 、 Timeout time Configuration etc. .

AbstractCheckHandler nextHandler Is the reference of the next processor held by the current processor , When the current processor finishes executing , Then call nextHandler Execute the next processor handle() Verification method ;

protected Result next() Is defined in an abstract class , Execute the method of the next processor , Use protected Statement , Each subclass processor holds the object . When the subclass processor finishes executing ( adopt ) when , Call the method of the parent class to execute the next processor nextHandler.

HandlerClient Is the client that executes the processor link ,HandlerClient.executeChain() Method is responsible for initiating the whole link call , And receive the return value of the processor link .


Roll up your sleeves and start rolling up the code ~

Commodity parameter object : Save the input parameters of the product

ProductVO It is the parameter object of creating goods , Contains the basic information of the product . And it is the input of multiple processors in the responsibility chain mode , Multiple processors are in ProductVO Perform specific logical processing for input parameters . In real business , Commodity objects are particularly complex . Let's simplify things , The simplified product parameters are as follows :

/** *  Commodity object  */
@Data
@Builder
public class ProductVO {
    
    /** *  goods SKU, only  */
    private Long skuId;
    /** *  Name of commodity  */
    private String skuName;
    /** *  Product picture path  */
    private String imgPath;
    /** *  Price  */
    private BigDecimal price;
    /** *  stock  */
    private Integer stock;
}

Abstract class processor : Abstract behavior , Subclasses have common attributes 、 Method

AbstractCheckHandler: Processor abstract class , And use @Component The annotation is registered as Spring Managed Bean object , The advantage of this is , We can use it easily Spring To manage these processors Bean.

/** *  Abstract class processor  */
@Component
public abstract class AbstractCheckHandler {
    

    /** *  The current processor holds a reference to the next processor  */
    @Getter
    @Setter
    protected AbstractCheckHandler nextHandler;


    /** *  Processor configuration  */
    @Setter
    @Getter
    protected ProductCheckHandlerConfig config;

    /** *  Processor execution method  * @param param * @return */
    public abstract Result handle(ProductVO param);

    /** *  Link delivery  * @param param * @return */
    protected Result next(ProductVO param) {
    
        // The next link has no processor , Go straight back to 
        if (Objects.isNull(nextHandler)) {
    
            return Result.success();
        }

        // Execute the next processor 
        return nextHandler.handle(param);
    }

}

stay AbstractCheckHandler Abstract class processor , Use protected Declare the visible properties and methods of subclasses . Use @Component annotation , Declare it as Spring Of Bean object , The advantage of this is that you can take advantage of Spring Easily manage all subclasses , Below you will see how to use . The properties and methods of the abstract class are described as follows :

  • public abstract Result handle(): Represents an abstract verification method , Every processor should inherit AbstractCheckHandler Abstract class processor , Pay equal attention to write them handle Method , Each processor thus realizes special verification logic , It's actually polymorphic Thought .

  • protected ProductCheckHandlerConfig config: Represents the dynamics of each processor Configuration class , Can pass “ Configuration center ” Dynamically modify the configuration , Implement processor “ Dynamic arrangement ” and “ Sequence control ”. Configuration class can be configured The name of the processor Next processor 、 as well as processor Whether to downgrade Equal attribute .

  • protected AbstractCheckHandler nextHandler: Indicates that the current processor holds a reference to the next processor , If the current processor handle() The verification method is completed , Then execute the next processor nextHandler Of handle() The verification method executes the verification logic .

  • protected Result next(ProductVO param): This method is used for processor link delivery , After the subclass processor finishes executing , Calling the next() The method is implemented in config The next processor on the configured link , If all processors are finished , The result is returned .

ProductCheckHandlerConfig Configuration class :

/** *  Processor configuration class  */
@AllArgsConstructor
@Data
public class ProductCheckHandlerConfig {
    
    /** *  processor Bean name  */
    private String handler;
    /** *  Next processor  */
    private ProductCheckHandlerConfig next;
    /** *  Whether to downgrade  */
    private Boolean down = Boolean.FALSE;
}

Subclass processor : Handle the unique verification logic

AbstractCheckHandler Abstract class processors have 3 The subcategories are :

  1. NullValueCheckHandler: Null check processor
  2. PriceCheckHandler: Price verification processing
  3. StockCheckHandler: Inventory verification processor

Each processor inherits AbstractCheckHandler Abstract class processor , Pay equal attention to write them handle() Processing method to realize the unique verification logic .

  • NullValueCheckHandler: Null check processor . Pertinence check the required parameters in the creation of goods . If the verification fails , The error code is returned ErrorCode, The chain of responsibility is broken here ( stop it ), Create a product and return the verified error message . Note the degraded configuration in the code super.getConfig().getDown() Is to obtain AbstractCheckHandler Configuration information stored in the processor object , If the processor is configured with degradation , Then skip the processor , call super.next() Execute the next processor logic .

Again , Use @Component Registered as Spring Managed Bean object ,

/** *  Null check processor  */
@Component
public class NullValueCheckHandler extends AbstractCheckHandler{
    

    @Override
    public Result handle(ProductVO param) {
    
        System.out.println(" Null check  Handler  Start ...");
        
        // Downgrade : If downgrade is configured , Then skip this processor , Execute the next processor 
        if (super.getConfig().getDown()) {
    
            System.out.println(" Null check  Handler  Degraded , Skip null check  Handler...");
            return super.next(param);
        }
        
        // Parameter is required 
        if (Objects.isNull(param)) {
    
            return Result.failure(ErrorCode.PARAM_NULL_ERROR);
        }
        //SkuId Commodity primary key parameter is required 
        if (Objects.isNull(param.getSkuId())) {
    
            return Result.failure(ErrorCode.PARAM_SKU_NULL_ERROR);
        }
        //Price Price parameter must be verified 
        if (Objects.isNull(param.getPrice())) {
    
            return Result.failure(ErrorCode.PARAM_PRICE_NULL_ERROR);
        }
        //Stock Inventory parameter is required 
        if (Objects.isNull(param.getStock())) {
    
            return Result.failure(ErrorCode.PARAM_STOCK_NULL_ERROR);
        }
        
        System.out.println(" Null check  Handler  adopt ...");
        
        // Execute the next processor 
        return super.next(param);
    }
}


  • PriceCheckHandler: Price verification processing . Verify the price parameters of the created goods . Here is just a simple price judgment >0 The check , The actual business is more complicated , such as “ Price door ” These preventive measures .
/** *  Price verification processor  */
@Component
public class PriceCheckHandler extends AbstractCheckHandler{
    
    @Override
    public Result handle(ProductVO param) {
    
        System.out.println(" Price check  Handler  Start ...");

        // Illegal price verification 
        boolean illegalPrice =  param.getPrice().compareTo(BigDecimal.ZERO) <= 0;
        if (illegalPrice) {
    
            return Result.failure(ErrorCode.PARAM_PRICE_ILLEGAL_ERROR);
        }
        // Other verification logic ...

        System.out.println(" Price check  Handler  adopt ...");

        // Execute the next processor 
        return super.next(param);
    }
}

  • StockCheckHandler: Inventory verification processor . Verify the inventory parameters of the created goods .
/** *  Inventory verification processor  */
@Component
public class StockCheckHandler extends AbstractCheckHandler{
    
    @Override
    public Result handle(ProductVO param) {
    
        System.out.println(" Inventory verification  Handler  Start ...");

        // Illegal inventory verification 
        boolean illegalStock = param.getStock() < 0;
        if (illegalStock) {
    
            return Result.failure(ErrorCode.PARAM_STOCK_ILLEGAL_ERROR);
        }
        // Other verification logic ..

        System.out.println(" Inventory verification  Handler  adopt ...");

        // Execute the next processor 
        return super.next(param);
    }
}

client : Execute processor link

HandlerClient The client class is responsible for initiating the execution of the entire processor link , adopt executeChain() Method . If the processor link returns an error message , That is, the verification fails , Then the entire link is truncated ( stop it ), Return the corresponding error message .

public class HandlerClient {
    

  public static Result executeChain(AbstractCheckHandler handler, ProductVO param) {
    
      // Execution processor 
      Result handlerResult = handler.handle(param);
      if (!handlerResult.isSuccess()) {
    
          System.out.println("HandlerClient  If the execution of the responsibility chain fails, return :" + handlerResult.toString());
          return handlerResult;
      }
      return Result.success();
  }
}

above , The classes related to the responsibility chain pattern have been created . Next, you can create products .


Create merchandise : Abstract steps , Change numerous for brief

createProduct() The method of creating goods is abstracted as 2 A step :① Parameter checking ② Create merchandise . Parameter verification uses the responsibility chain mode for verification , contain : Null check Price check Inventory verification wait , Only all processors on the chain pass the verification , Only called saveProduct() Create product method ; Otherwise, the verification error message is returned . stay createProduct() In the method of creating goods , adopt The chain of responsibility model , We will check the logic decoupling .createProduct() In the method of creating goods No need to pay attention Which verification processors must pass , And the details of the verification processor .

/** *  Create merchandise  * @return */
@Test
public Result createProduct(ProductVO param) {
    

    // Parameter checking , Use the responsibility chain model 
    Result paramCheckResult = this.paramCheck(param);
    if (!paramCheckResult.isSuccess()) {
    
        return paramCheckResult;
    }

    // Create merchandise 
    return this.saveProduct(param);
}

Parameter checking : The chain of responsibility model

Parameter checking paramCheck() Method use the responsibility chain mode to verify the parameters , The method does not declare the specific verifications , Which parameter verification logic is through multiple Processor chain passing Of . as follows :

/** *  Parameter checking : The chain of responsibility model  * @param param * @return */
private Result paramCheck(ProductVO param) {
    

    // Get processor configuration : Generally, the unified configuration center is used for storage , Support dynamic change 
    ProductCheckHandlerConfig handlerConfig = this.getHandlerConfigFile();

    // Get processor 
    AbstractCheckHandler handler = this.getHandler(handlerConfig);

    // Responsibility chain : Execute processor link 
    Result executeChainResult = HandlerClient.executeChain(handler, param);
    if (!executeChainResult.isSuccess()) {
    
        System.out.println(" Create merchandise   Failure ...");
        return executeChainResult;
    }

    // Processor links are all successful 
    return Result.success();
}

paramCheck() The method steps are described as follows :

step 1: Get processor configuration .

adopt getHandlerConfigFile() Method to get the processor configuration class object , The configuration class saves the configuration of the upper and lower nodes of each processor in the chain , Support Process planning Dynamic expansion . Usually, the configuration is through Ducc( JD self developed configuration center )Nacos( Alibaba open source configuration center ) And so on , Support Dynamic change In real time . Based on this , We can realize the arrangement of the verification processor 、 And dynamically expand . I do not use the configuration of the storage processor link in the configuration center , But use JSON Simulate configuration in the form of string , What you are interested in can be realized by yourself .

/** *  Get processor configuration : Generally, the unified configuration center is used for storage , Support dynamic change  * @return */
private ProductCheckHandlerConfig getHandlerConfigFile() {
    
    // Configuration stored in the configuration center 
    String configJson = "{\"handler\":\"nullValueCheckHandler\",\"down\":true,\"next\":{\"handler\":\"priceCheckHandler\",\"next\":{\"handler\":\"stockCheckHandler\",\"next\":null}}}";
    // Turn into Config object 
    ProductCheckHandlerConfig handlerConfig = JSON.parseObject(configJson, ProductCheckHandlerConfig.class);
    return handlerConfig;
}

ConfigJson Storage processor link configuration JSON strand , It may not be easy to see in the code , We can use json.cn Wait for formatting , as follows , The rules of the whole call link configured are particularly clear .

 Insert picture description here
getHandlerConfigFile() The structure of the configuration class obtained by the class is as follows , You can see , The configuration rules stored in the configuration center , Convert to configuration class ProductCheckHandlerConfig object , For program processing . Be careful , At this time, only the processor is stored in the configuration class SpringBean Of name nothing more , Not an actual processor object .

 Please add a picture description
Next , Get the actual processor to be executed through the configuration class .

step 2: Get the processor according to the configuration .

above step 1 adopt getHandlerConfigFile() Method after obtaining the processor link configuration rules , Call again getHandler() Get processor .
getHandler() The parameters are as above ConfigJson Configured rules , namely step 1 Converted ProductCheckHandlerConfig object ; according to ProductCheckHandlerConfig The configuration rule is converted to Processor link object . The code is as follows :

/** *  Use Spring Inject : All inherited AbstractCheckHandler The abstract class Spring Bean It's going to pour in .Map Of Key Corresponding Bean Of name,Value yes name Corresponding to the corresponding Bean */
@Resource
private Map<String, AbstractCheckHandler> handlerMap;

/** *  Get processor  * @param config * @return */
private AbstractCheckHandler getHandler (ProductCheckHandlerConfig config) {
    
    // Configuration check : The processor link is not configured , The verification logic is not executed 
    if (Objects.isNull(config)) {
    
        return null;
    }
    // Configuration error 
    String handler = config.getHandler();
    if (StringUtils.isBlank(handler)) {
    
        return null;
    }
    // Configured a non-existent processor 
    AbstractCheckHandler abstractCheckHandler = handlerMap.get(config.getHandler());
    if (Objects.isNull(abstractCheckHandler)) {
    
        return null;
    }
    
    // Processor settings configuration Config
    abstractCheckHandler.setConfig(config);
    
    // Set the link processor recursively 
    abstractCheckHandler.setNextHandler(this.getHandler(config.getNext()));

    return abstractCheckHandler;
}

step 2-1: Configuration check .

Code 14~27 That's ok , Some check operations of configuration are carried out . If the configuration is wrong , Then the corresponding processor cannot be obtained . Code 23 That's ok handlerMap.get(config.getHandler()) Is mapped from all processors Map Get the corresponding processor in Spring Bean.

Pay attention to the 5 Line code ,handlerMap All processor mappings are stored , It's through Spring@Resource Annotation injection Come in . The rule of injection is : All inherited AbstractCheckHandler abstract class ( It is Spring Managed Bean) Subclasses of ( Subclasses are also Spring Managed Bean) It's going to pour in .

Infused with handlerMap in Map Of Key Corresponding Bean Of name,Value yes name Corresponding Bean example , That is, the actual processor , Here it means Null check processor Price verification processor Inventory verification processor . as follows :
 Please add a picture description
According to the configuration ConfigJson step 1: Get processor configuration ) in handler:"priceCheckHandler" Configuration of , Use handlerMap.get(config.getHandler()) You can get the corresponding processor SpringBean Object .

step 2-2: Save processor rules .

Code 29 That's ok , Save the configuration rule to the corresponding processor abstractCheckHandler.setConfig(config), Subclass processors hold the rules of configuration .

step 2-3: Recursively set the processor link .

Code 32 That's ok , Recursively set the processor on the link .

// Set the link processor recursively abstractCheckHandler.setNextHandler(this.getHandler(config.getNext()));

This step may not be easy to understand , combination ConfigJson According to the configured rules , It seems very easy to understand .

 Insert picture description here

From top to bottom ,NullValueCheckHandler The null check processor passed setNextHandler() Method to set the processor of the next node that you own , That is, the price processor PriceCheckHandler.

next ,PriceCheckHandler Price processor , It also needs to go through step 2-1 Configuration check step 2-2 Save configuration rules , And most importantly , It also needs to set the processor of the next node StockCheckHandler Inventory verification processor .

StockCheckHandler The same is true for the inventory verification processor , It also needs to go through step 2-1 Configuration check step 2-2 Save configuration rules , But please pay attention to StockCheckHandler Configuration of , its next The rules are configured null, This means that there is no processor to execute under it , It is the last processing node on the whole link .

Called recursively getHandler() Get the processor method , The whole processor link object is connected in series . as follows :
 Please add a picture description

Friendship tips Recursion is fragrant , But when using recursion, we must pay attention to the condition processing of truncating recursion , Otherwise, it may cause a dead cycle !

actually ,getHandler() obtain Processor object The code of is to configure the rules in the configuration center ConfigJson, Convert to configuration class ProductCheckHandlerConfig object , Then according to the configuration class object , Convert to the actual processor object , This processor object Hold the call sequence of the whole link .


step 3: The client executes the call link .

public class HandlerClient {
    

  public static Result executeChain(AbstractCheckHandler handler, ProductVO param) {
    
      // Execution processor 
      Result handlerResult = handler.handle(param);
      if (!handlerResult.isSuccess()) {
    
          System.out.println("HandlerClient  If the execution of the responsibility chain fails, return :" + handlerResult.toString());
          return handlerResult;
      }
      return Result.success();
  }
}

getHandler() After getting the processor , The execution sequence of the whole call link is also determined , here , The client should work

HandlerClient.executeChain(handler, param) The method is HandlerClient The client class executes the entire call link of the processor , And receive the return value of the processor link .

executeChain() adopt AbstractCheckHandler.handle() Trigger the whole link processor to execute in sequence , If a processor fails the verification !handlerResult.isSuccess(), Error message returned ; All processors passed the verification , Then the correct information is returned Result.success().


summary : Cascade method call process

Based on the above , Then review the whole call process through the flowchart .

 Insert picture description here


test : Code execution result

scene 1: Create merchandise There is a null value in the parameter ( as follows skuId Parameter is null), The link was truncated by the null processor , Return error message

// Create product parameters 
ProductVO param = ProductVO.builder()
      .skuId(null).skuName(" Huawei mobile phones ").imgPath("http://...")
      .price(new BigDecimal(1))
      .stock(1)
      .build();

test result

 Insert picture description here

scene 2: Create merchandise Abnormal price parameters ( as follows price Parameters ), Truncated by price processor , Return error message

ProductVO param = ProductVO.builder()
      .skuId(1L).skuName(" Huawei mobile phones ").imgPath("http://...")
      .price(new BigDecimal(-999))
      .stock(1)
      .build();

test result

 Insert picture description here

scene 3: Create merchandise Abnormal inventory parameters ( as follows stock Parameters ), Truncated by inventory processor , Return error message .

// Create product parameters , Simulate user incoming 
ProductVO param = ProductVO.builder()
      .skuId(1L).skuName(" Huawei mobile phones ").imgPath("http://...")
      .price(new BigDecimal(1))
      .stock(-999)
      .build();

test result

 Insert picture description here

scene 4: Create merchandise All processors passed the verification , Save item .

// Create product parameters , Simulate user incoming 
ProductVO param = ProductVO.builder()
      .skuId(1L).skuName(" Huawei mobile phones ").imgPath("http://...")
      .price(new BigDecimal(999))
      .stock(1).build();

test result

 Insert picture description here



Case 2 : workflow , Expense reimbursement review process

My colleague Xiao Jia just came back from a business trip recently , She can't wait to submit the expense reimbursement process . Depending on the amount , It is divided into the following audit processes . The reimbursement amount is less than 1000 element , The three-level department manager can approve ,1000 To 5000 In addition to the approval of three-level department managers , It also needs the approval of secondary department managers , and 5000 To 10000 Yuan also needs the approval of the first level department manager . There are the following situations :

  1. Xiao Jia needs to be reimbursed 500 element , The three-level department manager can approve .
  2. Xiao Jia needs to be reimbursed 2500 element , After the approval of the manager of the third level Department , It also needs the approval of secondary department managers , After the approval of the manager of the secondary Department , To complete the reimbursement approval process .
  3. Xiao Jia needs to be reimbursed 7500 element , After the approval of the three-level manager , And after the approval of the secondary manager , The process flows to the first level department manager for approval , After being approved by the first level manager , That is, the reimbursement process is completed .
     Insert picture description here

### UML chart

AbstractFlowHandler As a processor abstract class , Abstract the approve() Audit method , Class A 、 second level 、 The third level department manager processor inherits the abstract class , Pay equal attention to write them approve() Audit method , So as to realize the unique audit logic .

 Insert picture description here

The configuration class is as follows , The processor of each layer should be configured with reviewers 、 Price review rules ( The biggest audit 、 Minimum amount )、 Next level handler . Configuration rules can be changed dynamically , If the amount that the third level department manager can audit increases to 2000 element , Modify the configuration to take effect dynamically .

 Insert picture description here

The code implementation is similar to case 1 , If you are interested, please use your hands ~



Four 、 The advantages and disadvantages of the responsibility chain

 Insert picture description here

 Insert picture description here

5、 ... and 、 Source check

github:https://github.com/rongtao7/MyNotes


If you like this article , Please don't be stingy with your praise , It's not easy to create , thank !

 Please add a picture description

原网站

版权声明
本文为[The sound of the waves is still loud]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/200/202207170010139339.html