为什么要用策略模式?何时用策略模式?
策略模式根据教科书式中的介绍为定义一系列算法,把它们一个个封装起来,并且使它们之间可互相替换,从而让算法可以独立于使用它的用户而变化。
在实际业务开发中,算法一般表现为一系列特定的业务操作,根据特定的业务策略执行相应的“算法”。一般在以下场景可以考虑使用策略模式:
- 选择语句(如
if/else、switch
)的多,选择执行块中操作各不相同且之后可能会进行业务扩展 - 避免选择语句导致的难以维护
- 想提高代码逼格
自定义式的策略模式演变
策略模式1.0 - 教材式的使用过程
- 将具体的算法封装到特定的策略接口具体实现类方法中
- 设定一个Context类封装策略操作接口,使用策略时传具体的执行策略实现类实例到Context中
例:
new Context(new ConcreteAStrategy()).handle(obj);
策略模式2.0 - 业务开发的使用过程(结合Spring)
- 将传统作为选择语句的条件设为算法切换的策略,通过特定常量或枚举包装
- 将选择执行块中的业务操作设为算法,从执行块中抽离封装到特定的策略操作类方法中
- 设定一个Context类,封装所有策略常量与对应处理类的映射,需要处理时传特定策略与处理对象(如VO)
例:
context.handle(strategy,vo);
策略模式-Wilson.0 - 无策略模式(点题)
- 将选择执行块中的业务操作设为算法,从执行块中抽离封装到特定的策略操作类方法中
- 设定一个Context类,封装所需映射,处理时只需传处理对象???策略常量不定义了???
context.handle(msg);
自定义无策略模式例子
诞生场景
众所周知,WebSocket是一种在单个TCP连接上进行全双工通信的协议,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
WebSocket比较常见的应用场景有服务器推送、聊天室等,而个人在最近遇到了不同消息需要特定的处理需求(也可作为WebSocket消息路由分发的一种解决方案),于是就想到了策略模式,结合了Spring与目前个人的见解搭建了以下策略模式结构:
看到这里相信大家都觉得我不讲码德了,明明上面的msgType
就是策略,为什么说无策略呢?且听我下文辩解。
编码设计
设计实现思路
设计思路:
- 个人认为,其实很多策略常量的设定都会与一些类的定义有所重叠的,且这些策略都是为了让程序运行时能识别执行哪些行为罢了,所以何不把这块谁当交给程序去做?
- 如果交给程序策略去做,那么肯定是需要约定的,之后的开发最好也遵循这些约定,COC(Convention over configuration)嘛,说起来也高逼格点
- 虽然程序定义好了策略常量了,但是程序得会找到这些常量的处理策略
实现思路:
- 为了方便消息的处理与转换,先为所有消息类定义一个基类
WebSocketMessage
,将msgType
设到基类中 - 既然由程序去定义策略常量(
msgType
),那么这些常量最好是基于现有需要处理的消息去演化(如不同消息的命名) - 既然要由现在处理类去演化,那么最好这些类命名都遵循一个约定,既然都继承
WebSocketMessage
,那就父类名作为后缀
在本例中我把消息类的前缀作为了策略常量,即msgTypeMap
中的key
,value
则是实际的消息Class
,基类下的子类不可能出现同名情况(有的话就是开发问题了),所以也不用考虑策略命名冲突问题 - 虽然程序定义好了策略常量(
msgType
)了,但策略处理类(MessageHandler
)不做点特殊处理依旧无法与这些策略常量匹配上,所以就想到了用泛型作为策略处理方法的参数 - 仅仅通过泛型在编码时是无法获取参数的类的,但Java的泛型是伪泛型,会在编译时进行类型擦除,擦除完我就可以拿到这个策略处理参数的实际Class了,没错,正是反射
于是就有了以下映射:
/**
* Map{消息类名前缀:消息类Class}
*/
private Map<String, Class<? extends WebSocketMessage>> msgTypeMap;
/**
* Map{消息类名前缀:消息类对应的handler}
*/
private Map<String, MessageHandler> messageHandlerMap;
程序的实现流程:
初始化:MessageContext
获取所有的MessageHandler
类,遍历时通过反射获取参数设置msgTypeMap
与messageHandlerMap
消息处理:
- 调用
MessageContext.convertJsonToMessage(msgJson)
将WebSocket传参的msgType
根据messageHandlerMap
转换msgJson
成相应消息对象 - 调用
MessageContext.handlerMsg(websocketMessage)
,messageHandlerMap根据实际的消息Class
得到策略名获取对应的MessageHandler进行消息处理
编码
WebSocketMessage
@Data
@Accessors(chain = true)
public abstract class WebSocketMessage implements Serializable {
public static final String MSG_TYPE = "msgType";
public static final String MSG_TYPE_SEPARATOR = "WebSocket";
private String msgType;
protected String content;
}
MessageContext
@Component
@Slf4j
@SuppressWarnings({"rawtypes", "unchecked"})
public class MessageContext implements ApplicationContextAware {
/**
* Map{消息类名前缀:消息类Class}
*/
private Map<String, Class<? extends WebSocketMessage>> msgTypeMap;
/**
* Map{消息类名前缀:消息类对应的handler}
*/
private Map<String, MessageHandler> messageHandlerMap;
private Map<String, ChannelService> channelServiceMap;
/**
* 初始化:注入灵魂,映射初始化
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.channelServiceMap = applicationContext.getBeansOfType(ChannelService.class);
// 获取容器中的所有MessageHandler
Map<String, MessageHandler> handlerBeanMap = applicationContext.getBeansOfType(MessageHandler.class);
this.messageHandlerMap = new HashMap<>(handlerBeanMap.size());
this.msgTypeMap = new HashMap<>(handlerBeanMap.size());
handlerBeanMap.values()
.forEach(messageHandler -> {
Class msgClass = Arrays.stream(
Arrays.stream(messageHandler.getClass().getMethods())
.filter(method -> Objects.equals(method.getName(), "handleMsg") && method.getParameterCount() == 1)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(messageHandler.getClass().getName() + " didn't implement handleMsg()"))
.getParameterTypes())
.filter(WebSocketMessage.class::isAssignableFrom)
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(messageHandler.getClass().getName() + " strategy init failed"));
String msgClassName = msgClass.getSimpleName();
this.messageHandlerMap.put(msgClassName, messageHandler);
String msgType = getMsgType(msgClassName);
this.msgTypeMap.put(msgType, msgClass);
});
log.info("websocket message context init completed, msg type: {}, handler map: {}", msgTypeMap, messageHandlerMap);
}
/**
* 获取所有的消息类型
*
* @return
*/
public Set<String> msgTypes() {
return msgTypeMap.keySet();
}
/**
* 检测字符串是否为有效的json字符串并包含消息类型msgType
*
* @param msgJson
* @return
*/
public boolean isValidMessage(String msgJson) {
......
}
/**
* 根据消息类Class.simpleName获取实际策略名,如BroadcastWebSocketMessage策略名为Broadcast
* @param msgClassName
* @return
*/
private String getMsgType(String msgClassName) {
return msgClassName.contains(WebSocketMessage.MSG_TYPE_SEPARATOR) ?
StringUtils.substringBefore(msgClassName, WebSocketMessage.MSG_TYPE_SEPARATOR) : msgClassName;
}
public MessageHandler getMessageHandler(String msgType) {
if (msgTypeMap.containsKey(msgType)) {
return messageHandlerMap.get(msgTypeMap.get(msgType).getSimpleName());
}
throw new IllegalArgumentException("msg type[" + msgType + "] handler doesn't exist");
}
public Object handleMessage(WebSocketMessage message) {
return getMessageHandler(message.getMsgType()).handleMsg(message);
}
public Object handleMessage(String msgJson) {
if (JSONValidator.from(msgJson).validate()) {
JSONObject jsonObject = JSON.parseObject(msgJson);
String msgType = jsonObject.getString(WebSocketMessage.MSG_TYPE);
MessageHandler messageHandler = getMessageHandler(msgType);
WebSocketMessage message = JSONObject.parseObject(msgJson, msgTypeMap.get(msgType));
return messageHandler.handleMsg(message);
} else {
throw new IllegalArgumentException("invalid msg json");
}
}
public Object handleMessage(JSONObject jsonObject) {
String msgType = jsonObject.getString(WebSocketMessage.MSG_TYPE);
Assert.isTrue(msgTypeMap.containsKey(msgType), "msgType [" + msgType + "] doesn't exist");
WebSocketMessage msgRequest = jsonObject.toJavaObject(msgTypeMap.get(msgType));
MessageHandler messageHandler = messageHandlerMap.get(msgRequest.getClass().getSimpleName());
return messageHandler.handleMsg(msgRequest);
}
public <T extends WebSocketMessage> WebSocketMessage convertJsonToMessage(JSONObject msgJson) {
String msgType = msgJson.getString(WebSocketMessage.MSG_TYPE);
return msgJson.toJavaObject(msgTypeMap.get(msgType));
}
/**
* remove channel from the all channel services
*
* @param channel
*/
public void removeChannel(Channel channel) {
channelServiceMap.values()
.forEach(channelService -> channelService.removeChannel(channel));
}
}
MessageHandler
/**
* @author Wilson
*/
public interface MessageHandler<T extends WebSocketMessage> {
Object handleMsg(T msg);
}
@Service
public class DirectionMessageHandler implements MessageHandler<DirectionWebSocketMessage> {
@Override
public Object handleMsg(DirectionWebSocketMessage msg) {
return "DirectionWebSocketMessage handles msg";
}
}
@Service
public class BroadcastMessageHandler implements MessageHandler<BroadcastWebSocketMessage> {
@Override
public Object handleMsg(BroadcastWebSocketMessage msg) {
return "BroadcastMessageHandler handles msg";
}
}
补个Controller测下MessageContext
@RestController
@RequestMapping("/message-context")
public class MessageContextController {
@Resource
private MessageContext messageContext;
@GetMapping("/msg-types")
@ApiOperation("所有的消息类型")
public ServerResponse<?> msgTypes() {
return ServerResponse.success(messageContext.msgTypes());
}
@PostMapping("/direction")
public ServerResponse<?> handleDirectionMsg(@RequestBody DirectionWebSocketMessage message) {
return ServerResponse.success(messageContext.handleMessage(message));
}
@PostMapping("/broadcast")
public ServerResponse<?> handleBroadcastMsg(@RequestBody BroadcastWebSocketMessage message) {
return ServerResponse.success(messageContext.handleMessage(message));
}
}
测试JSON:
/direction:
{
"msgType": "Direction"
}
/broadcast:
{
"msgType": "Broadcast"
}
与2.0的区别
- 初始化时比较麻烦,去掉了策略常量定义一步
- 扩展时只需定义新的消息类与新的处理类即可,如GroupWebSocketMessage与GroupMessageHandler
引出天坑
当MessageHandler的子类扩展了其他的方法(如实现其它接口)时反射获取到的消息参数Class可能会是WebSocketMessage而非实际的WebSocketMessage子类,如BroadcastMessageHandler
实现了ChannelService
后会出现以下初始化问题:
@Service
public class BroadcastMessageHandler implements MessageHandler<BroadcastWebSocketMessage>, ChannelService {
@Override
public Object handleMsg(BroadcastWebSocketMessage msg) {
System.out.println("BroadcastMessageHandler handles msg");
return "BroadcastMessageHandler handles msg";
}
@Override
public void addChannel(Channel channel) {
}
@Override
public void removeChannel(Channel channel) {
}
}
反射初始化错误日志(msgType丢失了Broadcast,多了个空字符串""):
websocket message context init completed, msg type: {=class per.wilson.chat.websocket.msg.request.WebSocketMessage, Direction=class per.wilson.chat.websocket.msg.request.DirectionWebSocketMessage}, handler map: {WebSocketMessage=per.wilson.chat.websocket.strategy.BroadcastMessageHandler@1d54b3, DirectionWebSocketMessage=per.wilson.chat.websocket.strategy.DirectionMessageHandler@1f40ca0}
该问题的原因目前只通过原生环境下反射初始化排除了框架问题,且非必现,有时会正常初始化:
websocket message context init completed, msg type: {Broadcast=class per.wilson.chat.websocket.msg.request.BroadcastWebSocketMessage, Direction=class per.wilson.chat.websocket.msg.request.DirectionWebSocketMessage}, handler map: {BroadcastWebSocketMessage=per.wilson.chat.websocket.strategy.BroadcastMessageHandler@21f43f, DirectionWebSocketMessage=per.wilson.chat.websocket.strategy.DirectionMessageHandler@10a133d}
当只实现MessageHandler
一个接口方法时,则必然不会出现以上问题,目前猜测是反射执行与类加载的时序问题,欢迎填坑。
养老程序员要保证月更真难,文中的websocket留下个月吧
转载可以,copy必究