跳到主要内容

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

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

    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();
}

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