一、实际开发痛点
当业务需求涉及到很多的分支逻辑时,过多的if-else或者过多的switch代码显得很不简洁,而且当我们修改某一分支逻辑时,要改动的很多,比如现在某一分支不要了,就要把这个if条件删除掉,这样是很不好的,这个时候,我们想到了策略模式,替换掉过多的if-else或者switch语句。
二、什么是策略模式
策略这个词应该怎么理解,打个比方说,我们出门的时候会选择不同的出行方式,比如骑自行车、坐公交、坐火车、坐飞机、坐火箭等等,这些出行方式,每一种都是一个策略。
再比如我们去逛商场,商场现在正在搞活动,有打折的、有满减的、有返利的等等,其实不管商场如何进行促销,说到底都是一些算法,这些算法本身只是一种策略,并且这些算法是随时都可能互相替换的,比如针对同一件商品,今天打八折、明天满100减30,这些策略间是可以互换的。
策略模式(Strategy),定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。
三、实际应用
1、业务场景
现在需要接收来自某系统的回调,消息回调会有很多类型,比如私聊文本消息、私聊图片消息、新好友申请、私聊语音消息等,每一种消息类型,对应不同的逻辑处理,我们最开始是想直接if-else或者switch,走不同的分支逻辑搞定,但是这样扩展性不好,不易维护,这时我们想到了策略模式。
2、定义枚举类
枚举类存放各种回调类型,值代表的是具体实现类的类名,首字母这里小写。通过key就能得到对应的类beaName。
@Getter public enum MsgCallBackEnum { //新好友申请 NEWFRIEND_MSG_CALL_BACK(0, "newFriendMsgCallBackHandler"), //添加好友成功 ADDFRIENDSUCCESS_MSG_CALL_BACK(16, "addfriendSuccessMsgCallBackHandler"), //私聊文本消息 PRIVATECHATTEXT_MSG_CALL_BACK(5, "privateChatTextMsgCallBackHandler"), //私聊图片消息 PRIVATECHATIMAGE_MSG_CALL_BACK(6, "privateChatImageMsgCallBackHandler"), //私聊视频消息 privateChatVIDEO_MSG_CALL_BACK(7, "privateChatVideoMsgCallBackHandler"), //私聊语音消息 PRIVATECHATVOICE_MSG_CALL_BACK(8, "privateChatVoiceMsgCallBackHandler"); private Integer type; private String beanName; MsgCallBackEnum(Integer type, String beanName) { this.type = type; this.beanName = beanName; } public static String getBeanName(Integer type) throws BusinessException { for (MsgCallBackEnum msgCallBackStrategyEnum : MsgCallBackEnum.values()) { if (msgCallBackStrategyEnum.type.equals(type)) { return msgCallBackStrategyEnum.getBeanName(); } } return null; } }
3、定义抽象类
抽象类定义抽象方法handler,每一种逻辑处理类只需要继承这个抽象类就可以了。
public abstract class AbstractMsgCallBackHandler { public final Boolean process(JSONObject json) { return this.handler(json); } /** * 处理相应逻辑 * * @param json * @return */ protected abstract Boolean handler(JSONObject json); }
4、定义实现类
实现类具体处理业务逻辑,继承抽象类
@Component @Slf4j public class NewFriendMsgCallBackHandler extends AbstractMsgCallBackHandler { @Resource private CommonService commonService; @Override protected Boolean handler(JSONObject json) { JSONObject data = json.getJSONObject("data"); String wId = data.getString("wId"); String v1 = data.getString("v1"); String v2 = data.getString("v2"); Integer type = data.getInteger("scene"); Map<String, Object> param = new HashMap<>(); param.put("wId", wId); param.put("v1", v1); param.put("v2", v2); param.put("type", type); commonService.commonSendPost(param, UrlConstant.WK_ACCEPTUSER_URL); return Boolean.TRUE; } }
5、调用抽象方法
第一步,先把抽象类注入,这里以map的方式注入,key为beanName,值为具体的实现类。
@Resource private Map<String, AbstractMsgCallBackHandler> abstractMsgCallBackHandlerMap;
第二步、通过类型获取beanName,从枚举中获取value
String beanName = MsgCallBackEnum.getBeanName(messageType);
第三步、通过beanName获取抽象类
AbstractMsgCallBackHandler abstractMsgCallBackHandler = abstractMsgCallBackHandlerMap.get(beanName);
第四步、调用抽象类抽象方法,会自动指向实现类
abstractMsgCallBackHandler.process(json);
整体的代码大概是这样的:
@Slf4j @RestController @RequestMapping("/callBack") public class CallBackController { @Resource private WxAdminService wxAdminService; @Resource private Map<String, AbstractMsgCallBackHandler> abstractMsgCallBackHandlerMap; /** * 消息接收服务地址 * * @param msg * @return */ @TokenNeedless @PostMapping("/getMsgCallBack") public Result getMsgCallBack(@RequestBody String msg) { JSONObject json = JSONObject.parseObject(msg); //消息类型 Integer messageType = json.getInteger("messageType"); log.info("getMsgCallBack:{}", msg); String beanName = MsgCallBackEnum.getBeanName(messageType); AbstractMsgCallBackHandler abstractMsgCallBackHandler = abstractMsgCallBackHandlerMap.get(beanName); if (abstractMsgCallBackHandler == null) { return Result.ok(); } Boolean process = abstractMsgCallBackHandler.process(json); log.info("getMsgCallBack_process:{}", process); return Result.ok(); } }
这样以后有新的类型,直接写一个实现抽象类的类就可以了,代码藕和度下降很多。
四、总结
- 何时使用
一个系统有许多类,而区分它们的只是他们直接的行为时 - 方法
将这些算法封装成一个一个的类,任意的替换 - 优点
算法可以自由切换
避免使用多重条件判断(如果不用策略模式我们可能会使用多重条件语句,不利于维护)
扩展性良好,增加一个策略只需实现接口即可 - 缺点
策略类数量会增多,每个策略都是一个类,复用的可能性很小
所有的策略类都需要对外暴露 - 使用场景
多个类只有算法或行为上稍有不同的场景
算法需要自由切换的场景
需要屏蔽算法规则的场景