Skip to main content

多类型消息策略模式优化


多类型消息策略模式+工厂优化

我们在发送消息的时候,会有很多类型的消息要发送,具体类型枚举如下:

    TEXT(1, "正常消息"),
    RECALL(2, "撤回消息"),
    IMG(3, "图片"),
    FILE(4, "文件"),
    SOUND(5, "语音"),
    VIDEO(6, "视频"),
    EMOJI(7, "表情"),
    SYSTEM(8, "系统消息"),

每种类型的消息都会有自己的处理逻辑,最简单的办法就是使用if else来判断type是哪种类型,然后作出相应的处理,但是这样是会耦合在一起,也不满足开闭原则(对扩展开放,对修改关闭)

如果我们使用策略模式,就可以很好的解耦合,做到开闭原则

策略抽象类

先定义一个抽象的策略类:

public abstract class AbstractMsgHandler<Req> {
    @Autowired
    private MessageService messageService;
    private Class<Req> bodyClass;

    @PostConstruct
    private void init() {
        ParameterizedType genericSuperclass = (ParameterizedType) this.getClass().getGenericSuperclass();
        this.bodyClass = (Class<Req>) genericSuperclass.getActualTypeArguments()[0];
        MsgHandlerFactory.register(getMsgTypeEnum().getType(), this);
    }

    abstract MessageTypeEnum getMsgTypeEnum();

    protected void checkMsg(Req body, Long roomId, Long uid) {

    }

    @Transactional
    public Long checkAndSaveMsg(ChatMessageReq request, Long uid) {
        Req body = this.toBean(request.getBody());
        // 统一校验
        AssertUtil.allCheckValidateThrow(body);
        // 子类扩展校验
        checkMsg(body, request.getRoomId(), uid);
        Message insert = MessageAdapter.buildMsgSave(request, uid);
        // 统一保存
        messageService.save(insert);
        // 子类扩展保存
        saveMsg(insert, body);
        return insert.getId();
    }

    private Req toBean(Object body) {
        if (bodyClass.isAssignableFrom(body.getClass())) {
            return (Req) body;
        }
        return BeanUtil.toBean(body, bodyClass);
    }

    protected abstract void saveMsg(Message message, Req body);

    /**
     * 展示消息
     */
    public abstract Object showMsg(Message msg);

    /**
     * 被回复时——展示的消息
     */
    public abstract Object showReplyMsg(Message msg);

    /**
     * 会话列表——展示的消息
     */
    public abstract String showContactMsg(Message msg);

}

实现细节:

这段代码定义了一个抽象类 AbstractMsgHandler<Req>,它是一个用于处理消息的基类。该类使用了泛型 Req 来表示消息的具体类型,并提供了一些通用的消息处理方法。以下是代码的详细解释:

  • private Class<Req> bodyClass;:用于保存泛型 Req 的具体类型。我们想要获取Req的字节码对象,但是它是一个范型,不可以直接获取,在init方法中,我们想办法去获取。
  • 在init方法上面有一个Spring的注解@PostConstruct,他表示Spring容器在加载的时候会执行这个函数。那么我们每个子类去继承这个抽象类AbstractMsgHandler的时候,都把子类注入到Spring容器中,子类就会有这个方法和PostConstruct这个注解,那么都会执行init方法,也就是接下来的执行工厂把自己注册到工厂中去,方便获取。

抽象方法;

  1. **abstract MessageTypeEnum getMsgTypeEnum();**返回消息的类型,具体实现由子类提供。
  2. **protected abstract void saveMsg(Message message, Req body);**保存消息的具体实现,由子类提供。
  3. **public abstract Object showMsg(Message msg);**展示消息的具体实现,由子类提供。
  4. **public abstract Object showReplyMsg(Message msg);**展示被回复时的消息,具体实现由子类提供。
  5. **public abstract String showContactMsg(Message msg);**展示会话列表中的消息,具体实现由子类提供。

方法

  1. **protected void checkMsg(Req body, Long roomId, Long uid) { ... }**进行消息校验,具体实现由子类扩展。
  2. **@Transactional public Long checkAndSaveMsg(ChatMessageReq request, Long uid) { ... }**主要流程:
    1. 将请求体转换为 Req 类型。
    2. 统一校验消息。
    3. 调用子类的 checkMsg 方法进行进一步校验。
    4. 创建并保存消息。
    5. 调用子类的 saveMsg 方法进行进一步保存。
    6. 返回消息的 ID。
  3. **private Req toBean(Object body) { ... }**将请求体转换为 Req 类型的对象。
  • 初始化:在类实例化后,获取泛型的具体类型,并注册到工厂中。
  • 消息校验和保存:提供统一的消息校验和保存逻辑,具体的校验和保存由子类实现。
  • 消息展示:提供抽象方法,让子类实现具体的消息展示逻辑。

这块代码的设计模式主要是模板方法模式(Template Method Pattern),它定义了一个算法的骨架,而将一些步骤延迟到子类中去实现。

不同策略实现

然后去实现不同种的策略:

image-20240528200822607

例如表情包的实现:

@Component
public class EmojisMsgHandler extends AbstractMsgHandler<EmojisMsgDTO> {
    @Autowired
    private MessageService messageService;

    @Override
    protected MessageTypeEnum getMsgTypeEnum() {
        return MessageTypeEnum.EMOJI;
    }

    @Override
    public void saveMsg(Message msg, EmojisMsgDTO body) {
        MessageExtra extra = Optional.ofNullable(msg.getExtra()).orElse(new MessageExtra());
        Message update = new Message();
        update.setId(msg.getId());
        update.setExtra(extra);
        extra.setEmojisMsgDTO(body);
        messageService.updateById(update);
    }

    @Override
    public Object showMsg(Message msg) {
        return msg.getExtra().getEmojisMsgDTO();
    }

    @Override
    public Object showReplyMsg(Message msg) {
        return "表情";
    }

    @Override
    public String showContactMsg(Message msg) {
        return "[表情包]";
    }
}

策略工厂

public class MsgHandlerFactory {
    private static final Map<Integer, AbstractMsgHandler> STRATEGY_MAP = new HashMap<>();

    public static void register(Integer code, AbstractMsgHandler strategy) {
        STRATEGY_MAP.put(code, strategy);
    }

    public static AbstractMsgHandler getStrategyNoNull(Integer code) {
        AbstractMsgHandler strategy = STRATEGY_MAP.get(code);
        AssertUtil.isNotEmpty(strategy, CommonErrorEnum.PARAM_VALID);
        return strategy;
    }
}

MsgHandlerFactory 工厂类,用于管理和获取不同类型的消息处理器 AbstractMsgHandler 实例。具体解释如下:

  1. private static final Map<Integer, AbstractMsgHandler> STRATEGY_MAP = new HashMap<>();:一个静态的 HashMap,用于存储消息处理器实例。键是消息类型的整数代码,值是对应的消息处理器实例。
  2. public static void register(Integer code, AbstractMsgHandler strategy) { ... }:将消息处理器注册到 STRATEGY_MAP 中。
  3. public static AbstractMsgHandler getStrategyNoNull(Integer code) { ... }:根据消息类型代码从 STRATEGY_MAP 中获取消息处理器实例。

使用策略

@Override
@Transactional
public Long sendMsg(ChatMessageReq request, Long uid) {
    check(request, uid);
    // todo 保存消息
    AbstractMsgHandler<?> msgHandler = MsgHandlerFactory.getStrategyNoNull(request.getMsgType());
    Long msgId = msgHandler.checkAndSaveMsg(request, uid);
    // 发布消息发送事件
    applicationEventPublisher.publishEvent(new MessageSendEvent(this, msgId));
    return msgId;
}

解释:

  1. 使用 MsgHandlerFactory.getStrategyNoNull(request.getMsgType()) 方法,根据消息类型获取对应的消息处理器 AbstractMsgHandler 实例。这一步是策略模式的核心,通过消息类型动态选择合适的消息处理策略。
  2. 调用获取到的消息处理器的 checkAndSaveMsg 方法,对消息进行校验并保存。这里的 msgHandler 可以是任何具体的消息处理器(如 TextMsgHandlerImageMsgHandler),具体的实现细节由各个消息处理器类定义。
@Transactional
public Long checkAndSaveMsg(ChatMessageReq request, Long uid) {
    Req body = this.toBean(request.getBody());
    // 统一校验
    AssertUtil.allCheckValidateThrow(body);
    // 子类扩展校验
    checkMsg(body, request.getRoomId(), uid);
    Message insert = MessageAdapter.buildMsgSave(request, uid);
    // 统一保存
    messageService.save(insert);
    // 子类扩展保存
    saveMsg(insert, body);
    return insert.getId();
}

这里的处理流程都是一样的,也可以抽象出来。