Spring 与策略模式双剑合壁,让你彻底的消灭冗余的if...else
实际业务场景
最近在项目中需要开发消息发送平台,需要根据 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 SendMessage(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... 也是一种更高效且便于维护的方式。
作者:Recluse
链接:https://juejin.cn/post/7026366743115202567