责任链
这在订单中使用。如果你一下子去看订单里的责任链,可能会很懵。没关系,接下来我们将通过一个示例来讲解如何在项目里运用责任链。
定义
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为型模式。 在这种模式中,通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
简单来说就是职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递。
什么时候使用它
当某块业务逻辑既要又要还要且还怎样时,每个逻辑有明显的判断逻辑。这时候考虑使用它。
优缺点
优点:
- 降低耦合度。它将请求的发送者和接收者解耦。
- 简化了对象。使得对象不需要知道链的结构。
- 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除。
- 增加新的请求处理类很方便。
缺点:
- 不能保证请求一定被接收。
- 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
- 可能不容易观察运行时的特征,有碍于除错。
核心实现
- 抽象处理者(Handler)
定义一个处理请求的接口,通常包含一个处理请求的方法(如 handleRequest)和一个指向下一个处理者的引用(后继者)。
- 具体处理者(ConcreteHandler)
实现了抽象处理者接口,负责处理请求。如果能够处理该请求,则直接处理;否则,将请求传递给下一个处理者。
- 客户端(Client)
创建处理者对象,并将它们连接成一条责任链。通常,客户端只需要将请求发送给责任链的第一个处理者,无需关心请求的具体处理过程。
示例
需求是,促销某个业务时,需要发送一条消息到站内消息、订阅微信通知的用户、APP推送、短信其他等等。
像这种需求就是“既要又要还要且还怎样时”
想象一下,如果不使用任何设计模式,是不是可以很轻松的写出如下代码:
private final MessageTemplateService messageTemplateService;
public void send(Message message) {
MessageTemplate messageTemplate = messageTemplateService.get();
if(messageTemplate.enable(站内消息)) {
// 发送站内消息
}
if(messageTemplate.enable(发送微信通知)) {
// 发送微信通知
}
if(messageTemplate.enable(发送APP)) {
// 发送APP
}
if(messageTemplate.enable(发送短信)) {
// 发送短信
}
}
当然了,这种写法违背了各种开发原则。如果我们要使用责任链来优化上面的代码,我们又应该要怎么做呢?
为了更好的实现通用性,先定义一个抽象接口:
/**
* 抽象业务责任链组件
*
* @author mjyang
* @date 2023/5/23 17:03
*/
public interface AbstractChainHandler<T> extends Ordered {
/**
* 执行责任链逻辑
*
* @param t 责任链执行入参
*/
void handler(T t);
/**
* 责任链组件标识
*
* @return 责任链组件标识
*/
String mark();
}
注意 mark 方法标识,通用责任链将会通过这个标识来串起链。同时,需要注意的是,接口实现了 Ordered 接口,这个主要是用来给链排序使用。
先来定义一个通用的抽象处理者:
/**
* 抽象责任链上下文
*
* @author mjyang
* @date 2023/5/23 17:03
*/
@Component
public final class AbstractChainContext<T> implements CommandLineRunner {
private final Map<String, List<AbstractChainHandler>> abstractChainHandlerContainer = MapUtil.newHashMap();
/**
* 责任链组件执行
*
* @param mark 责任链组件标识
* @param t 请求参数
*/
public void handler(String mark, T t) {
List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(mark);
if (CollectionUtil.isEmpty(abstractChainHandlers)) {
throw new RuntimeException(String.format("%s chain of id is undefined.", mark));
}
abstractChainHandlers.forEach(handler -> handler.handler(t));
}
@Override
public void run(String... args) {
Map<String, AbstractChainHandler> chainFilterMap = SpringUtil.getBeansOfType(AbstractChainHandler.class);
chainFilterMap.forEach((beanName, bean) -> {
List<AbstractChainHandler> abstractChainHandlers = abstractChainHandlerContainer.get(bean.mark());
if (CollectionUtils.isEmpty(abstractChainHandlers)) {
abstractChainHandlers = new ArrayList<>();
}
abstractChainHandlers.add(bean);
List<AbstractChainHandler> actualAbstractChainHandlers = abstractChainHandlers.stream()
.sorted(Comparator.comparing(Ordered::getOrder))
.collect(Collectors.toList());
abstractChainHandlerContainer.put(bean.mark(), actualAbstractChainHandlers);
});
}
}
为了方便使用前面提到的 mark。我们可以先定义一个接口来表示:
/**
* 消息发送计算链
*
* @author mjyang
* @date 2024/2/23 10:17
*/
public interface MessagePushChainFilter<T extends MessagePushDto> extends AbstractChainHandler<T> {
/**
* 责任链组件标识
*
* @return 责任链组件标识
*/
@Override
default String mark() {
return "message_push";
}
}
再来定义具体链:
/**
* 站内消息推送处理器
*
* @author mjyang
* @date 2022/10/31 14:06
*/
@Component
@Slf4j
public class MessagePushSiteMessageHandler implements MessagePushChainFilter<MessagePushDto> {
@Override
public void handler(MessagePushDto context) {
...
}
@Override
public int getOrder() {
return 1;
}
}
/**
* 微信推送处理器
*
* @author mjyang
* @date 2022/10/31 14:06
*/
@Component
@Slf4j
public class MessagePushWxMaHandler implements MessagePushChainFilter<MessagePushDto> {
@Override
public void handler(MessagePushDto context) {
...
}
@Override
public int getOrder() {
return 2;
}
}
接下来就是客户端调用了:
/**
* 消息推送应用服务类
*
* @author mjyang
* @date 2023/6/27 14:45
*/
@Service
@RequiredArgsConstructor
public class MessagePushServiceImpl implements MessagePushService {
private final MessageTemplateRepository messageTemplateRepository;
private final AbstractChainContext<MessagePushDto> messagePushHandlerChain;
@Override
public void push(MessagePushDto dto) {
MessageTemplate messageTemplate = this.messageTemplateRepository.get(dto.getNotifyBizType(), dto.getNotifyBizSubType());
messagePushHandlerChain.handler("message_push", dto);
}
}
通过这样的一个改造。我们将业务和发送逻辑相分离,后续如果需要在新增发送者,我们只需要在增加一个 messageHandler 即可,核心流程代码都不用改动。