介绍
责任链设计模式(Chain of Responsibility Pattern)是一种行为型设计模式,用于构建处理请求的对象链。在这种模式中,请求从链的一端开始,然后依次经过链中的各个处理器(或处理节点),直到找到能够处理请求的处理器为止。每个处理器都决定是否能够处理请求,如果可以处理,则处理请求,否则将请求传递给下一个处理器。这种方式可以实现请求的分发和处理,同时使代码更加灵活和可扩展。
责任链模式包含以下主要角色:
-
抽象处理器(Handler):定义一个处理请求的接口,通常包括一个处理方法(或处理请求的抽象方法),以及一个可选的后继处理器引用。具体的处理器类将实现这个接口。
-
具体处理器(ConcreteHandler):实现抽象处理器接口,在处理请求时,如果自己可以处理,则处理请求;否则将请求传递给下一个处理器。每个具体处理器知道自己的责任范围,并决定是否继续传递请求。
-
客户端(Client):创建请求对象并将其发送到责任链的第一个处理器。客户端通常不知道具体的处理器是如何处理请求的,它只需要将请求发送给责任链即可。
主要优点包括:
-
松耦合(Loose Coupling):责任链模式将请求的发送者和接收者解耦,使得发送者不需要知道具体的接收者,从而提高了系统的灵活性。
-
可扩展性:可以轻松地向责任链中添加新的处理者或修改现有的处理者,而不需要修改已有的代码,这有助于系统的扩展和维护。
-
职责分离:责任链模式将不同的职责分散到不同的处理者中,每个处理者只关注自己的职责,使系统更易理解和维护。
-
动态链:责任链可以在运行时动态构建,可以根据请求的类型和条件来决定责任链的组成,从而实现灵活的链路构建。
-
递归处理:责任链模式支持递归处理,当一个处理者无法处理请求时,可以将请求传递给下一个处理者,直到找到合适的处理者为止。
-
可过滤性:可以在责任链中的某个处理者中决定是否终止整个请求处理链,这样可以根据需要决定是否继续传递请求。
-
可定制性:每个具体的处理者可以根据需求自定义处理逻辑,从而实现不同的处理策略。
-
透明性:客户端无需关心请求的具体处理过程,只需要将请求发送到责任链的起始点即可,责任链内部的细节对客户端透明。
使用场景
-
审批流程: 一个典型的场景是请假审批流程。不同级别的管理者(如组长、经理、总经理)可以处理请假请求,每个级别的管理者负责处理自己级别以下的请求。如果一个管理者无法处理请求,请求会继续传递给下一个级别的管理者。
-
日志记录系统: 在日志记录系统中,可以创建多个日志处理器,每个处理器负责处理不同级别的日志信息。例如,一个处理器可以记录所有日志,另一个可以只记录错误日志。日志消息从高到低级别经过责任链中的处理器,每个处理器决定是否处理该消息。
-
权限验证: 在Web应用中,可以使用责任链模式来验证用户的权限。每个处理器可以检查用户的权限,并决定是否允许用户访问某个资源。如果一个处理器无法验证权限,请求会传递给下一个处理器。
-
异常处理: 在异常处理系统中,可以定义多个异常处理器,每个处理器负责处理不同类型的异常。当系统抛出异常时,异常会经过责任链中的处理器,每个处理器可以选择捕获并处理异常,或将异常传递给下一个处理器。
-
购物车订单处理: 在电子商务网站中,可以使用责任链模式来处理购物车订单。每个处理器可以检查订单中的商品是否有库存、是否符合促销规则等。如果一个处理器无法处理订单中的某个商品,它可以将该商品从订单中移除,并将订单传递给下一个处理器。
-
工作流程管理: 在工作流程管理系统中,可以使用责任链模式来定义不同的工作流程步骤。每个步骤可以有一个处理器,负责执行特定的任务。工作流程中的任务可以按照预定义的顺序依次执行。
-
请求过滤器: 在Web应用中,可以使用责任链模式来实现请求过滤器。每个过滤器可以处理特定类型的请求,例如身份验证、日志记录、跨域处理等。多个过滤器可以组成一个责任链,以依次处理请求。
-
链式调用: 在编程中,责任链模式也可以用于构建链式调用的API。每个方法可以返回一个包含下一个方法的对象,从而实现链式调用。
这些场景和案例展示了责任链设计模式的多样性和实用性。责任链模式允许您构建灵活、可扩展且可维护的系统,使不同的处理逻辑可以根据需要组合和配置。
示例
以下是一个使用Java代码示例来演示责任链设计模式的简单请假审批系统:
// 抽象处理器
abstract class Approver {
protected Approver successor; // 后继处理者
public void setSuccessor(Approver successor) {
this.successor = successor;
}
public abstract void processLeaveApplication(LeaveApplication application);
}
// 具体处理器1:组长
class TeamLead extends Approver {
public void processLeaveApplication(LeaveApplication application) {
if (application.getDays() <= 1) {
System.out.println("Team Lead approved leave for " + application.getDays() + " days");
} else if (successor != null) {
successor.processLeaveApplication(application);
}
}
}
// 具体处理器2:经理
class Manager extends Approver {
public void processLeaveApplication(LeaveApplication application) {
if (application.getDays() <= 5) {
System.out.println("Manager approved leave for " + application.getDays() + " days");
} else if (successor != null) {
successor.processLeaveApplication(application);
}
}
}
// 具体处理器3:总经理
class GeneralManager extends Approver {
public void processLeaveApplication(LeaveApplication application) {
if (application.getDays() <= 10) {
System.out.println("General Manager approved leave for " + application.getDays() + " days");
} else {
System.out.println("Leave application rejected for " + application.getDays() + " days");
}
}
}
// 请假申请
class LeaveApplication {
private int days;
public LeaveApplication(int days) {
this.days = days;
}
public int getDays() {
return days;
}
}
public class Main {
public static void main(String[] args) {
// 创建责任链
Approver teamLead = new TeamLead();
Approver manager = new Manager();
Approver generalManager = new GeneralManager();
teamLead.setSuccessor(manager);
manager.setSuccessor(generalManager);
// 提交请假申请
LeaveApplication application1 = new LeaveApplication(1);
teamLead.processLeaveApplication(application1);
LeaveApplication application5 = new LeaveApplication(5);
teamLead.processLeaveApplication(application5);
LeaveApplication application10 = new LeaveApplication(10);
teamLead.processLeaveApplication(application10);
LeaveApplication application15 = new LeaveApplication(15);
teamLead.processLeaveApplication(application15);
}
}
在上面的示例中,我们创建了三个具体的处理器(TeamLead、Manager、GeneralManager),它们分别处理不同级别的请假申请。每个处理器都可以批准一定天数内的请假,否则将请求传递给下一个处理器。
在Main
类中,我们创建了责任链并提交了不同天数的请假申请,最终得到相应的批准结果。
这个示例展示了责任链设计模式的用法,它使得请求可以依次经过不同的处理者,每个处理者根据自己的规则来处理请求,从而实现了请求的分发和处理。
项目应用
背景:运营商号卡选号下单需要根据优先级和不同的规则匹配号池,匹配顺序是身份证生日->下单手机后四位->号池权重->随机选号,只要匹配到号码就返回,否则会继续匹配直到最后一个规则,基于面向对象的开闭原则和类的单一职责,也为了后续基于需求变动可以灵活拓展,故采用责任链设计模式。
- 抽象处理器(Handler)
/**
* @description 号卡下单匹配手机号处理器抽象类
* @author youmu
* @date 2023/8/10 10:23
*/
@Data
@Component
@Slf4j
public abstract class AbstractMatchPhoneHandler {
public AbstractMatchPhoneHandler nextHandler;
/**
* @description 按优先级匹配手机号
* @author youmu
* @date 2023/8/10 10:25
* @param pair 下单参数
* @return 下一个处理器
*/
public AbstractMatchPhoneHandler doFilter(Pair<PlaceSIMCardOrderRO, Product> pair) {
String result = null;
try{
result = getMatchPhone(pair);
}catch (Exception e) {
log.error(e.getMessage(), e);
}
pair.getLeft().setHandleNo(result);
return doNextHandler(pair);
}
/**
* @description 获取匹配手机号
* @author youmu
* @date 2023/8/10 14:07
*/
public abstract String getMatchPhone(Pair<PlaceSIMCardOrderRO, Product> pair);
/**
* @description 执行下一个处理器
* @author youmu
* @date 2023/8/10 10:29
* @param pair 下单参数
* @return void
*/
private AbstractMatchPhoneHandler doNextHandler(Pair<PlaceSIMCardOrderRO, Product> pair) {
if (nextHandler != null && StringUtils.isBlank(pair.getLeft().getHandleNo())) {
return nextHandler.doFilter(pair);
}
// 执行到最后一个处理器或匹配到手机号直接返回
return nextHandler;
}
}
- 具体处理器(ConcreteHandler)
在责任链设计模式中,@Order
注解可以用来控制责任链中不同责任对象的执行顺序。责任链模式通常由多个处理器(责任对象)组成,每个处理器负责处理特定类型的请求,并且它们按照一定的顺序执行。
/**
* @description 手机号匹配处理-身份证号生日
* @author youmu
* @date 2023/8/10 10:43
*/
@RequiredArgsConstructor
@Service
@Order(100)
@Slf4j
public class BirthdayMatchPhoneHandler extends AbstractMatchPhoneHandler {
private final RedissonClient redissonClient;
@Override
public String getMatchPhone(Pair<PlaceSIMCardOrderRO, Product> pair) {
String pid = pair.getRight().getSupplierConfig().split(",")[0];
PlaceSIMCardOrderRO ro = pair.getLeft();
List<PhoneInfoDTO> list = redissonClient.getList(CacheNamesConstant.JUNBO_PHONE_SORTED_NUM_LIST + "_" + pid + "_" + ro.getProvince() + "_" + ro.getCity());
// 身份证出生月日
String birthday = ro.getIdCardNo().substring(10,14);
Optional<PhoneInfoDTO> optional = list.stream().filter(item->item.getPhoneNumber().contains(birthday)).findFirst();
String result = optional.map(PhoneInfoDTO::getPhoneNumber).orElse(null);;
log.info("【自动下单手机匹配】匹配身份证生日,pid={},province={},city={},result={}",pid,ro.getProvince(),ro.getCity(),result);
return result;
}
}
/**
* @description 手机号匹配处理-权重
* @author youmu
* @date 2023/8/10 10:43
*/
@RequiredArgsConstructor
@Service
@Order(200)
@Slf4j
public class WeightMatchPhoneHandler extends AbstractMatchPhoneHandler {
private final RedissonClient redissonClient;
@Override
public String getMatchPhone(Pair<PlaceSIMCardOrderRO, Product> pair) {
String pid = pair.getRight().getSupplierConfig().split(",")[0];
PlaceSIMCardOrderRO ro = pair.getLeft();
List<PhoneInfoDTO> phoneList = redissonClient.getList(CacheNamesConstant.JUNBO_PHONE_SORTED_NUM_LIST + "_" + pid + "_" + ro.getProvince() + "_" + ro.getCity());
List<PhoneInfoDTO> list = phoneList.stream().filter(item->!item.isJumpMatch()).collect(Collectors.toList());
if(CollectionUtils.isEmpty(list)) {
return null;
}
Collections.shuffle(list);
String result = list.get(0).getPhoneNumber();
log.info("【自动下单手机匹配】匹配权重,pid={},province={},city={},result={}",pid,ro.getProvince(),ro.getCity(),result);
return result;
}
}
/**
* @description 手机号匹配处理-下单号码后4位
* @author youmu
* @date 2023/8/10 10:43
*/
@RequiredArgsConstructor
@Service
@Order(300)
@Slf4j
public class OrderMatchPhoneHandler extends AbstractMatchPhoneHandler {
private final RedissonClient redissonClient;
@Override
public String getMatchPhone(Pair<PlaceSIMCardOrderRO, Product> pair) {
String pid = pair.getRight().getSupplierConfig().split(",")[0];
PlaceSIMCardOrderRO ro = pair.getLeft();
List<PhoneInfoDTO> list = redissonClient.getList(CacheNamesConstant.JUNBO_PHONE_SORTED_NUM_LIST + "_" + pid + "_" + ro.getProvince() + "_" + ro.getCity());
// 下单手机后四位
String contactNumber = ro.getContactNumber();
String str = contactNumber.substring(ro.getContactNumber().length() -4);
Optional<PhoneInfoDTO> optional = list.stream().filter(item->item.getPhoneNumber().contains(str)).findFirst();
String result = optional.map(PhoneInfoDTO::getPhoneNumber).orElse(null);
log.info("【自动下单手机匹配】匹配下单号码后四位,pid={},province={},city={},result={}",pid,ro.getProvince(),ro.getCity(),result);
return result;
}
}
/**
* @description 手机号匹配处理-默认
* @author youmu
* @date 2023/8/10 10:43
*/
@Service
@Order(400)
@Slf4j
public class DefaultMatchPhoneHandler extends AbstractMatchPhoneHandler {
@Override
public String getMatchPhone(Pair<PlaceSIMCardOrderRO, Product> pair) {
return "";
}
}
/**
* @description 号卡下单匹配手机号责任链
* @author youmu
* @date 2023/8/10 10:52
*/
@Service
@Getter
public class MatchPhoneChainHandler {
@Autowired
private List<AbstractMatchPhoneHandler> chains;
public AbstractMatchPhoneHandler firstHandler;
@PostConstruct
private void constructChain() {
if (CollectionUtils.isEmpty(chains)) {
throw new RuntimeException("not found match phone chain handler");
}
firstHandler = chains.get(0);
for (int i = 0; i < chains.size(); i++) {
if (i == chains.size() - 1) {
chains.get(i).setNextHandler(null);
} else {
chains.get(i).setNextHandler(chains.get(i + 1));
}
}
}
}
- 客户端(Client)
/**
* 号卡下单
*
* @param ro 参数
* @param placeOrderType 下单类型
* @param matchPhoneHandler 重推下单Handler
* @param isReport 是否上报
* @return 结果
*/
private PlaceCardOrderResultVO placeCardOrder(PlaceSIMCardOrderRO ro, PlaceOrderTypeEnum placeOrderType,
AbstractMatchPhoneHandler matchPhoneHandler, boolean isReport) {
// 获取广告参数
OceanMacroParam oceanMacroParam = OceanUtil.getOceanMacroParam(ro);
// 设置下单方式
ro.setPlaceOrderType(placeOrderType.getCode());
Triple<Product, SupplierInfoVO, SupplierApiStrategy> params = buildParams(ro.getProductId());
// 判断是否为号卡
if (!params.getLeft().getType().equals(ProductBusinessTypeEnum.HK.getValue())) {
throw new BizException(CodeEnum.NOT_FOUND);
}
// 获取产品信息
Product product = params.getLeft();
// 处理选号责任链
if (Objects.nonNull(matchPhoneHandler)) {
matchPhoneHandler = matchPhoneHandler.doFilter(Pair.of(ro, product));
}
// 具体业务逻辑。。。
// 执行重推下单责任链
if (matchPhoneHandler != null && !result.getSuccess()) {
return placeCardOrder(ro, AUTO_RETRY, matchPhoneHandler, isReport);
}
return result;
}