前言
当我们写业务代码的时候,经常可以在工程里看到很多坨下面这样的if else代码。如果想把手机通知加进来,就要再加个else if,然后再里面写业务代码。这样的方式代码臃肿,难以维护,不易于扩展。那么有什么办法可以写出更优雅的代码呢?
所以我们引入策略模式来解决这个问题。废话不多说,先看代码。
策略工厂
通知策略工厂,用于获取具体通知策略Bean对象,比如短信通知策略对象/邮件通知策略对象/微信通知策略对象。因为大多数我们使用Spring进行管理Bean,所以我们可以把通知方式和Bean对象维护在一个Map中。
/**
* @author ZhangYao
* @description NoticeFactory
* @date Create in 16:04 2023/5/23
*/
@Service
public class NoticeFactory {
/**
* 存储通知方式和策略Bean关系
*/
public static Map<String, NoticeStrategy> NOTICE_STRATEGY_MAP = new HashMap<>();
/**
* 注册策略Bean
*/
public static void registerBean(String noticeType, NoticeStrategy noticeStrategy) {
NOTICE_STRATEGY_MAP.put(noticeType, noticeStrategy);
}
/**
* 获取策略Bean
*/
public NoticeStrategy getInstance(String noticeType) {
NoticeStrategy strategy = NOTICE_STRATEGY_MAP.get(noticeType);
if (strategy == null) {
strategy = NOTICE_STRATEGY_MAP.get(CoreConstants.NoticeType.DEFAULT);
}
return strategy;
}
}
策略接口
通知策略接口,目前只有一个发送消息的接口方法。
public interface NoticeStrategy {
default String sendMessage(String message) {
return "send message(" + message + ") successful.";
}
}
抽象策略类
抽象策略类,实现了通知策略接口和InitializingBean接口,InitializingBean接口用于Spring在创建Bean的流程中存储通知方式和策略Bean关系Map。
/**
* @author ZhangYao
* @description 通知抽象类
* @date Create in 11:38 2023/5/29
*/
public abstract class AbstractNoticeStrategyImpl implements NoticeStrategy, InitializingBean {
}
消息实体类
也就是入参对象封装,其中最重要的就是通知类型noticeType,其他字段都属于业务字段,比如消息内容,用户Id等等。
/**
* @author ZhangYao
* @description 消息实体类
* @date Create in 13:57 2023/5/29
*/
public class MessageEntity {
/**
* 通知类型:sms;email;weChat;
*/
private String noticeType;
/**
* 通知内容
*/
private String content;
/**
* 用户Id
*/
private String userId;
// ...
public String getNoticeType() {
return noticeType;
}
public void setNoticeType(String noticeType) {
this.noticeType = noticeType;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
}
上下文 Context
用于提供给其他调用方直接调用,符合开闭原则。
/**
* @author ZhangYao
* @description NoticeContext
* @date Create in 14:17 2023/5/23
*/
@Component
public class NoticeContext {
@Autowired
private NoticeFactory noticeFactory;
public String sendMessage(MessageEntity message) {
NoticeStrategy noticeStrategy = noticeFactory.getInstance(message.getNoticeType());
return noticeStrategy.sendMessage(message.getContent());
}
}
具体策略接口实现类
短信通知实现类
/**
* @author ZhangYao
* @description 短信通知实现类
* @date Create in 15:06 2023/5/23
*/
@Service
public class SmsNoticeStrategyImpl extends AbstractNoticeStrategyImpl {
/**
* 项目启动时,初始化当前类的Bean时调用
*/
@Override
public void afterPropertiesSet() {
NoticeFactory.registerBean(CoreConstants.NoticeType.SMS, this);
}
@Override
public String sendMessage(String message) {
return "send sms message(" + message + ") successful.";
}
}
邮件通知实现类
/**
* @author ZhangYao
* @description 邮件通知实现类
* @date Create in 11:34 2023/5/29
*/
@Service
public class EmailNoticeStrategyImpl extends AbstractNoticeStrategyImpl {
@Override
public void afterPropertiesSet() {
NoticeFactory.registerBean(CoreConstants.NoticeType.EMAIL, this);
}
@Override
public String sendMessage(String message) {
return "send email message(" + message + ") successful.";
}
}
微信通知实现类
/**
* @author ZhangYao
* @description 微信通知实现类
* @date Create in 11:34 2023/5/29
*/
@Service
public class WeChatNoticeStrategyImpl extends AbstractNoticeStrategyImpl {
@Override
public void afterPropertiesSet() {
NoticeFactory.registerBean(CoreConstants.NoticeType.WE_CHAT, this);
}
@Override
public String sendMessage(String message) {
return "send weChat message(" + message + ") successful.";
}
}
其他的策略实现就不贴出来了,道理都是一样的。
是在策略类Bean初始化时,把Bean放在工厂类的 Map。这样的好处是不需要单独维护Bean和noticeType的关系。
以后我们新增一个通知策略,比如 QQ 通知用户,只需要新增一个 QQNoticeStrategy 类即可,然后在这个类里指定具体的 noticeType 即可。
测试控制器
比如controller层需要调发送消息接口,直接调noticeContext的sendMessage方法。
@Controller
@RequestMapping("/notice")
public class NoticeController {
@Autowired
private NoticeContext noticeContext;
/**
curl --location --request POST 'localhost:8080/notice/send-message' \
--header 'Content-Type: application/json' \
--data-raw '{
"noticeType": "sms",
"content": "hi,boy!"
}'
*/
@ResponseBody
@PostMapping("/send-message")
public String sendMessage(@RequestBody CommonMessage message) {
if (message == null) {
message.setNoticeType(CoreConstants.NoticeType.SMS);
message.setContent("hello word!");
}
return noticeContext.sendMessage(message);
}
}
使用postman测试我们刚刚写的策略模式代码。当然我更推荐你使用单元测试。