设计模式在 IM 系统中的应用:工厂模式、策略模式、责任链模式
工厂模式:解决对象创建问题,实现命令处理器的自动注册策略模式:解决算法选择问题,不同命令使用不同处理策略责任链模式:解决请求传递问题,AI 消息按优先级依次处理实际使用建议1.工厂模式:适用于需要根据类型创建对象的情景2.策略模式:适用于有多种处理方式且需要动态选择的场景3.责任链模式:适用于需要按优先级一次处理请求的场景注意事项避免过度设计:简单场景不需要引入复杂模式性能考虑:反射和责任链遍历有
设计模式在 IM 系统中的应用:工厂模式、策略模式、责任链模式
在即时通讯系统中,合理使用设计模式能提升代码的维护性和扩展性。本文介绍 AQChat 中工厂模式、策略模式、责任链模式的应用与实践。
一、工厂模式:自动注册命令处理器
问题背景
系统需要处理多种命令(登录、发送消息、加入房间等)。如果手动维护命令与处理器的映射,新增命令需要修改多出代码,容易出错且不便于扩展。
解决方案:反射 + 工厂模式
AQChat 使用 AQChatHandlerFactory 实现命令处理器的自动注册:
@Component
public class AQChatHandlerFactory implements InitializingBean {
private final Map<Class<?>, ICmdHandler<? extends GeneratedMessageV3>> commandHandlerMap = new HashMap<>();
@Resource
private ApplicationContext applicationContext;
private void init() {
// 获取所有的 ICmdHandler 子类
Set<Class<?>> clazzSet = listSubClazz();
for (Class<?> cmdHandlerClazz : clazzSet) {
// 通过反射获取 handle 方法的第二个参数类型(即命令类型)
Method[] methodArray = cmdHandlerClazz.getDeclaredMethods();
Class<?> cmdClazz = null;
for (Method currMethod : methodArray) {
if (!currMethod.getName().equals("handle")) {
continue;
}
Class<?>[] paramTypeArray = currMethod.getParameterTypes();
if (paramTypeArray.length >= 2 &&
GeneratedMessageV3.class.isAssignableFrom(paramTypeArray[1])) {
cmdClazz = paramTypeArray[1];
break;
}
}
// 从 Spring 容器获取处理器实例,建立命令类型到处理器的映射
ICmdHandler<?> newHandler = (ICmdHandler<?>)
applicationContext.getBean(cmdHandlerClazz);
commandHandlerMap.put(cmdClazz, newHandler);
}
}
public ICmdHandler<? extends GeneratedMessageV3> getCommandHandler(Class<?> clazz) {
return commandHandlerMap.get(clazz);
}
}
核心机制
1.自动扫描:通过 Spring 的 ApplicationContext 获取所有 ICmdHandler 实现类
2.反射识别:通过反射获取 handle 方法的第二个参数类型,确定命令类型
3.自动映射:简历命令类型到处理器的映射关系
使用示例
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof GeneratedMessageV3) {
// 根据消息类型自动获取对应的处理器
ICmdHandler<?> handler = commandHandlerFactory.getCommandHandler(msg.getClass());
if (handler != null) {
handler.handle(ctx, castToMessage(msg));
}
}
}
优势
- 新增命令只需要实现
ICmdHandler,无需修改工厂类 - 通过反射自动建立映射,减少手动维护
- 统一管理所有命令处理器,便于维护
缺点
- 反射有性能开销(启动时一次性扫描,影响可忽略)
- 类型安全依赖运行时检查
二、策略模式:不同命令的不同处理策略
问题背景
不同命令的处理逻辑差异很大:登录需要生成用户 ID 并保存到 Redis,发送消息需要去重、广播、持久化。如果使用 if-else 或 switch,代码会臃肿且难以扩展。
解决方法:策略模式
定义统一的处理器接口:
public interface ICmdHandler<T extends GeneratedMessageV3> {
void handle(ChannelHandlerContext ctx, T cmd);
String verifyLogin(ChannelHandlerContext ctx);
String verifyJoinRoom(ChannelHandlerContext ctx);
}
不同命令实现不同的处理器策略:
登录命令处理器:
@Component
public class UserLoginCmdHandler extends AbstractCmdBaseHandler<AQChatMsgProtocol.UserLoginCmd> {
@Override
public void handle(ChannelHandlerContext ctx, AQChatMsgProtocol.UserLoginCmd cmd) {
// 生成用户ID
String userId = IdProvider.generateUserId();
// 保存用户信息到Redis
UserGlobalInfoDto userInfo = new UserGlobalInfoDto();
userInfo.setUserId(userId);
userInfo.setUserName(cmd.getUserName());
userHolder.saveUserInfo(userInfo);
// 建立用户与Channel的映射
globalChannelHolder.put(userId, (NioSocketChannel) ctx.channel());
// 返回登录成功响应
ctx.writeAndFlush(buildLoginAck(userId));
}
}
发送消息命令处理器:
@Component
public class SendMsgCmdHandler extends AbstractCmdBaseHandler<AQChatMsgProtocol.SendMsgCmd> {
@Override
public void handle(ChannelHandlerContext ctx, AQChatMsgProtocol.SendMsgCmd cmd) {
// 1. 消息去重检查
if (messageHolder.isExistMessageId(roomId, msgId)) {
return; // 消息已处理,直接返回
}
// 2. 构建消息对象
MessageDto messageDto = buildMessageDto(cmd);
// 3. 异步发送到房间(RocketMQ)
mqSendingAgent.sendMessageToRoom(messageDto);
// 4. 处理AI消息(责任链模式)
if (isAIMessage(roomInfo)) {
handlerFactory.handleMessage(messageDto);
}
// 5. 持久化消息
mqSendingAgent.storeMessages(messageDto);
// 6. 返回ACK
ctx.writeAndFlush(buildSendMsgAck());
}
}
策略选择
工厂模式负责根据命令类型选择对应的策略(处理器):
// 工厂根据消息类型返回对应的策略
ICmdHandler<?> handler = commandHandlerFactory.getCommandHandler(msg.getClass());
handler.handle(ctx, msg); // 执行对应的策略
优势
- 每个命令的处理逻辑独立,符合开闭原则
- 新增命令只需新增处理器类
- 代码结构清晰,易于维护和理解
缺点
- 类数量增加
- 需要统一接口设计
三、责任链模式:@AI 消息的处理流程
问题背景
系统需要支持多个 AI 助手(如@AI助手、@小M、@小T、@小V 等),以及 AI 空间的多轮对话。消息需要按优先级依次检查,匹配到对应处理器后停止传递。
解决方案:责任链模式
定义抽象处理器:
public abstract class Handler {
protected Handler handler; // 下一个处理器
public void setHandler(Handler handler) {
if (this.handler == null) {
this.handler = handler;
} else {
this.handler.setHandler(handler); // 递归设置,构建链
}
}
public abstract void handleMessage(MessageDto messageDto);
}
具体处理器实现:
@AI助手处理器(XQHandler):
@Component
public class XQHandler extends Handler {
@Override
public void handleMessage(MessageDto messageDto) {
String messageExt = messageDto.getMessageExt();
// 判断是否@了AI助手
if ((AT + AI_HELPER_ID).equals(messageExt)) {
// 处理AI助手消息
MessageDto aiMessage = copyMessage(messageDto);
aiMessage.setMessageContent(
messageDto.getMessageContent().replace(AT + AI_HELPER_NAME, "")
);
mqSendingAgent.aiHelper(aiMessage);
} else {
// 不匹配,传递给下一个处理器
if (handler != null) {
handler.handleMessage(messageDto);
}
}
}
}
多轮对话处理器(MultipleRoundsHandler):
@Component
public class MultipleRoundsHandler extends Handler {
@Override
public void handleMessage(MessageDto messageDto) {
RoomInfoDto roomInfo = roomHolder.getRoomInfoById(messageDto.getRoomId());
// 判断是否是AI空间且没有@特定AI
if (RoomType.AI.getCode() == roomInfo.getRoomType() &&
messageDto.getMessageExt().isEmpty()) {
// 处理多轮对话
MessageDto aiMessage = copyMessage(messageDto);
mqSendingAgent.multiple(aiMessage);
} else {
// 继续传递
if (handler != null) {
handler.handleMessage(messageDto);
}
}
}
}
责任链的构建:
@Component
public class HandlerFactory implements InitializingBean {
private Handler handler;
private void init() {
// 获取所有Handler子类
Map<String, Handler> handlerMap = applicationContext.getBeansOfType(Handler.class);
Set<Handler> handlerSet = new HashSet<>(handlerMap.values());
// 构建责任链
handlerSet.forEach(h -> {
if (handler == null) {
handler = h; // 第一个处理器作为链头
} else {
handler.setHandler(h); // 添加到链尾
}
});
}
public void handleMessage(MessageDto messageDto) {
handler.handleMessage(messageDto); // 从链头开始处理
}
}
处理流程示例
假设消息"@AI助手 你好"的处理流程:
1.XQHandler:检查到 messageExt = "@AI助手",匹配,处理并停止传递
2.如果 XQHandler 不匹配,传递给 XMHandler
3.如果 XMHandler 不匹配,传递给 XTHandler
4.依次类推,直到找到匹配的处理器或链结束
优势
- 解耦:每个处理器只关注自己的逻辑
- 灵活:可动态调整处理器顺序
- 扩展:新增 AI 助手只需新增处理器类
缺点
- 性能:需要遍历链直到找到匹配(通常链较短,影响可忽略)
- 调试;链式调用增加调试难度
四、三种模式的组合使用
在 AQChat 中,三种模式协同工作:
1.工厂模式:自动注册命令处理器,根据消息类型选择处理器
2.策略模式:不同命令使用不同处理策略
3.责任链模式:在发送消息命令中,对 AI 消息使用责任链进行路由
代码示例:
// SendMsgCmdHandler 中
public void handle(ChannelHandlerContext ctx, AQChatMsgProtocol.SendMsgCmd cmd) {
// ... 消息去重、广播等逻辑 ...
// 如果是AI消息,使用责任链模式处理
if (roomType || roomInfoDto.getAi() == SwitchStatusEnum.OPEN.getCode()) {
handlerFactory.handleMessage(messageDto); // 责任链处理
}
}
五、总结
- 工厂模式:解决对象创建问题,实现命令处理器的自动注册
- 策略模式:解决算法选择问题,不同命令使用不同处理策略
- 责任链模式:解决请求传递问题,AI 消息按优先级依次处理
实际使用建议
1.工厂模式:适用于需要根据类型创建对象的情景
2.策略模式:适用于有多种处理方式且需要动态选择的场景
3.责任链模式:适用于需要按优先级一次处理请求的场景
注意事项
- 避免过度设计:简单场景不需要引入复杂模式
- 性能考虑:反射和责任链遍历有开销,需权衡
- 代码可读性:模式应提升可读性,而非增加复杂度
通过合理使用设计模式,代码结构更清晰、扩展性更强、维护成本更低。在 IM 系统中,这三种模式的应用提升了系统的灵活性和可维护性。
更多推荐


所有评论(0)