当前位置:网站首页>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】

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@ResourceNote 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 .
3、 ... and 、 Application scenarios
The application scenario of the responsibility chain pattern , In practice , There are usually two application scenarios .
- The operation needs to go through a series of checks , After passing the verification, some operations are performed .
- 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 :

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 .
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 :
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 .

Case 1 actual combat : The responsibility chain mode realizes the creation of commodity verification
UML chart : The hills are small

AbstractCheckHandler Represents the processor abstract class , Responsible for abstracting processor behavior . Its have 3 A subclass , Namely :
NullValueCheckHandler: Null check processorPriceCheckHandler: Price verification processingStockCheckHandler: 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 inheritAbstractCheckHandlerAbstract class processor , Pay equal attention to write themhandleMethod , 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 processorhandle()The verification method is completed , Then execute the next processornextHandlerOfhandle()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 thenext()The method is implemented inconfigThe 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 :
NullValueCheckHandler: Null check processorPriceCheckHandler: Price verification processingStockCheckHandler: 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 returnedErrorCode, 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 obtainAbstractCheckHandlerConfiguration information stored in the processor object , If the processor is configured with degradation , Then skip the processor , callsuper.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 .

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 .

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 :
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 .

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 :
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 .

test : Code execution result
scene 1:
Create merchandiseThere is a null value in the parameter ( as followsskuIdParameter isnull), 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

scene 2:
Create merchandiseAbnormal price parameters ( as followspriceParameters ), 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

scene 3:
Create merchandiseAbnormal inventory parameters ( as followsstockParameters ), 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

scene 4:
Create merchandiseAll 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

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 :
- Xiao Jia needs to be reimbursed 500 element , The three-level department manager can approve .
- 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 .
- 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 .

### 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 .

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 .

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


5、 ... and 、 Source check
github:https://github.com/rongtao7/MyNotes
MyNotes: My summary notes
MyNotes-design:「 Actual design pattern 」 special column
- design-demo-chain: Practical responsibility chain mode
If you like this article , Please don't be stingy with your praise , It's not easy to create , thank !

边栏推荐
猜你喜欢

Understand network namespaces

Yum warehouse service and PXE automatic deployment system

5、AsyncTool框架竟然有缺陷?

梦开始的地方----初识c语言

Squid agent service deployment

What happens when you get stuck compiling and installing MySQL database in Linux system?

二进制安装kubernetes 1.23.2

MySQL master-slave replication + read write separation

Services for NFS

二进制安装kubernetes 1.24.1
随机推荐
Squid agent service deployment
HCIA's first static routing experiment
High quality subroutine
升级kubernetes 1.23.2到1.24.1
Test knowledge preparation
HCIA_RIP实验
Let's learn about awk~
Firewall firewall
RHCE8学习指南第一章 安装RHEL8.4
【NoSQL】NoSQL之redis配置与优化(简单操作)
win10网络连接显示无网络但可以上网
Detailed explanation of dynamic compression and static compression of gzip
RHCE8学习指南 第4章 获取帮助
RHCE ansible second operation
GFS distributed file system
学习网络基础
DHCP原理与配置
4、AsyncTool框架的一些思考
【MySQL】数据查询操作(select语句)
Learning network foundation