多类型消息策略模式优化
About 5 min
多类型消息策略模式+工厂优化
我们在发送消息的时候,会有很多类型的消息要发送,具体类型枚举如下:
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
方法,也就是接下来的执行工厂把自己注册到工厂中去,方便获取。
抽象方法;
- **
abstract MessageTypeEnum getMsgTypeEnum();
**返回消息的类型,具体实现由子类提供。 - **
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);
**展示会话列表中的消息,具体实现由子类提供。
方法
- **
protected void checkMsg(Req body, Long roomId, Long uid) { ... }
**进行消息校验,具体实现由子类扩展。 - **
@Transactional public Long checkAndSaveMsg(ChatMessageReq request, Long uid) { ... }
**主要流程:- 将请求体转换为
Req
类型。 - 统一校验消息。
- 调用子类的
checkMsg
方法进行进一步校验。 - 创建并保存消息。
- 调用子类的
saveMsg
方法进行进一步保存。 - 返回消息的 ID。
- 将请求体转换为
- **
private Req toBean(Object body) { ... }
**将请求体转换为Req
类型的对象。
- 初始化:在类实例化后,获取泛型的具体类型,并注册到工厂中。
- 消息校验和保存:提供统一的消息校验和保存逻辑,具体的校验和保存由子类实现。
- 消息展示:提供抽象方法,让子类实现具体的消息展示逻辑。
这块代码的设计模式主要是模板方法模式(Template Method Pattern),它定义了一个算法的骨架,而将一些步骤延迟到子类中去实现。
不同策略实现
然后去实现不同种的策略:
例如表情包的实现:
@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
实例。具体解释如下:
private static final Map<Integer, AbstractMsgHandler> STRATEGY_MAP = new HashMap<>();
:一个静态的HashMap
,用于存储消息处理器实例。键是消息类型的整数代码,值是对应的消息处理器实例。public static void register(Integer code, AbstractMsgHandler strategy) { ... }
:将消息处理器注册到STRATEGY_MAP
中。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;
}
解释:
- 使用
MsgHandlerFactory.getStrategyNoNull(request.getMsgType())
方法,根据消息类型获取对应的消息处理器AbstractMsgHandler
实例。这一步是策略模式的核心,通过消息类型动态选择合适的消息处理策略。 - 调用获取到的消息处理器的
checkAndSaveMsg
方法,对消息进行校验并保存。这里的msgHandler
可以是任何具体的消息处理器(如TextMsgHandler
或ImageMsgHandler
),具体的实现细节由各个消息处理器类定义。
@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();
}
这里的处理流程都是一样的,也可以抽象出来。