这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战
实际业务场景
最近在项目中需要开发消息发送平台,需要根据 type 值判断来进行相应的业务处理以及消息发送方式,这样就涉及到重复的 if-else 的问题,为了解决这样重复的代码来造成的代码冗余的问题,所以考虑使用策略模式,根据具体的场景执行对应的策略,来解决问题。本文简单写一个测试 Demo,实际开发需要根据具体业务,具体实现对应的业务逻辑。
具体实现
当我们遇到这样的逻辑处理时,第一反应是针对 type 进行 if...else... 或者是 switch 的逻辑判断,从而区分不同业务逻辑处理。
基于 if...else... 的伪代码
public int sendMessage(int type, Message message) {
if (type == 1) {
// 省略业务处理
return mail.sendMessage(message); // 进行邮件发送
} else if (type == 2) {
// 省略业务处理
return mobile.sendMessage(message); // 进行手机短信发送
} else if (type == 3) {
// 省略业务处理
return app.sendSendMessage(message); // 进行 app 消息推送
} else if (type == 4){
// ...
}
//...
}
假设上面的代码是用来对消息进行多渠道的发送,根据不同 type 来进行发送方式的选择。当然真实的业务场景不可能是这么简单的判断。
首先对照一下设计模式的开闭原则:面对扩展开放,面对修改关闭。
上述代码,如果某个发送方式改变了,那么这段代码就要进行修改,或者如果新增了一个发送方式,这段代码同样需要修改。一旦修改必然会影响到其他方式的业务逻辑。完全不符合开闭原则,同时代码中还充斥着大量的 if...else...,如果业务复杂,代码会急速膨胀。
那么,下面我们就针对以上实例,用策略模式来进行重新设计。
基于策略模式的伪代码
首先定义一个发送消息的接口 ISendMessageStrategy。实战过程中可根据具体情况采用接口或抽象类。
public interface ISendMessageStrategy {
/**
* 发送消息
*
* @param message 发送的消息内容
*/
void sendMessage(Message message);
}
在接口中提供一个方法,也就是发送消息的方法。这里因为是接口,所以定义的方法就需要子类必须实现。下面便是针对此接口的具体实现,不同的发送方式有不同的实现。
/**
* 邮件发送的实现
**/
@Slf4j
public class EmailSendMessageStrategy implemets ISendMessageStrategy {
@Override
public void sendMessage(Message message) {
// 省略业务处理
mail.sendMessage(message);
}
}
/**
* 短信发送的实现
**/
public class MobileSendMessageStrategy implemets ISendMessageStrategy {
@Override
public void sendMessage(Message message) {
// 省略业务处理
phone.sendMessage(message);
}
}
我们来实现一个持有接口 ISendMessageStrategy 的角色类 SendMessageHandler
public class SendMessageHandler {
/**
* 持有策略抽象类
*/
private ISendMessageStrategy sendMessageStrategy;
// 通过构造方法注入,也可以通过其他方式注入
public SendMessageHandler(ISendMessageStrategy sendMessageStrategy) {
this.sendMessageStrategy = sendMessageStrategy;
}
public void sendMessage(Message message) {
return sendMessageStrategy.sendMessage(message);
}
}
最后,我们来看一下如何调用该策略类
public class Test {
public static void main(String[] args) {
SendMessageHandler handler = new SendMessageHandler(new MobileSendMessageStrategy());
handler.sendMessage(new Message());
}
}
使用 Spring 来管理对象
上面的改进已经避免了大量的 if... else...,此时如果项目使用的是 Spring 项目,我们再进一步改进, 此时主要利用 Spring 的 @Autowired 注解来将实例化的策略实现类注入到一个 Map 当中,然后通过 key 可以方便的拿到服务。
首先将策略实现类通过@Service 注解进行实例化,并指定实例化的名称。以短信发送的实现为例:
@Component("mobileSendMessageStrategy")
public class MobileSendMessageStrategy implemets ISendMessageStrategy {
// ...
}
其他策略实现类与上相同,依次实例化。最后改造环境角色类为 SendMessageHandler:
@Component
public class SendMessageHandler implements ApplicationContextAware {
private final Map<String, ISendMessageStrategy> strategyMap = new ConcurrentHashMap<>();
public ISendMessageStrategy get(String beanName) {
return strategyMap.get(beanName);
}
@Override
public void setApplicationContext(ApplicationContext context) throws Exception {
strategyMap = applicationContext.getBeansOfType(ISendMessageStrategy.class);
}
}
applicationContext.getBeansOfType(ISendMessageStrategy.class) 会将容器中 ISendMessageStrategy 的实现类(注解了 @Component)放到该 map 中。其中 key 就是 @Component 中指定的实例化服务的名称,value 便是对应的对象。
public class Test {
@Resource
private SendMessageHandler messageHandler;
public void test() {
messageHandler.get("mobileSendMessageStrategy").sendMessage(new Message());
}
}
借助枚举类进一步优化
我们再进一步改进。我们不再在策略角色类中调用策略类的方法了,只让策略角色类作为工厂的角色,返回对应的服务。而相关服务方法的调用由客户端直接调用实现类的方法。
同时,针对服务的名称和类型我们通过枚举进行映射。先来定义一个枚举类:
public enum TypeEnum {
MAIL(0, "mailSendMessageStrategy", "邮件"),
MOBILE(1, "mobileSendMessageStrategy", "短信"),
APP(2, "appSendMessageStrategy", "app");
TypeEnum(int type, String serviceName, String desc) {
this.type = type;
this.serviceName = serviceName;
this.desc = desc;
}
public static TypeEnum valueOf(int type) {
for (TypeEnum typeEnum : TypeEnum.values()) {
if (typeEnum.getType() == type) {
return typeEnum;
}
}
return null;
}
private int type;
private String serviceName;
private String desc;
public int getType() {
return type;
}
public String getServiceName() {
return serviceName;
}
public String getDesc() {
return desc;
}
}
然后改造环境角色类为 SendMessageHandler:
@Component
public class SendMessageHandler implements ApplicationContextAware {
private final Map<String, ISendMessageStrategy> strategyMap = new ConcurrentHashMap<>();
public ISendMessageStrategy get(int type) {
TypeEnum typeEnum = TypeEnum.valueOf(type);
return strategyMap.get(typeEnum.getServiceName());
}
@Override
public void setApplicationContext(ApplicationContext context) throws Exception {
strategyMap = applicationContext.getBeansOfType(ISendMessageStrategy.class);
}
}
测试一下
public class Test {
@Resource
private SendMessageHandler messageHandler;
public void test() {
int type = 1;
messageHandler.get(type).sendMessage(new Message());
}
}
此时,如果新添加算法,只用创建对应算法的服务,然后在枚举类中映射一下关系,便可在不影响客户端调用的情况进行扩展。当然,根据具体的业务场景还可以进行进一步的改造。
总结
设计模式可以可以更好的扩展代码,但是一定程度上也加大了代码的阅读成本。所以在实际的项目中,不要一味的追求设计模式,要结合实际的业务情况进行合理的选择。当判断项固定且较少时,if...else... 也是一种更高效且便于维护的方式。