阅读 77

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

文章分类
代码人生
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐