设计模式在 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 系统中,这三种模式的应用提升了系统的灵活性和可维护性。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐