策略模式 我们真的需要吗 if else不香吗

813 阅读6分钟

策略模式

What‘s the problem? 有什么问题

不言而喻,过多的if-else代码和嵌套,会使阅读代码的人很难理解到底是什么意思。尤其是那些没有注释的代码。

可扩展/维护性差,因为if-else特别多,想要新加一个条件的时候,就会很难添加,极其容易影响到其他的if分支。

这种代码写起来简单,但明显违反了面向对象的 2 个基本原则:

  • 单一职责原则(一个类应该只有一个发生变化的原因):因为之后修改任何一个逻辑,当前类都会被修改
  • 开闭原则(对扩展开放,对修改关闭):如果此时需要添加(删除)某个逻辑,那么不可避免的要修改原来的代码

在《Java开发手册-华山版》的编程规约-控制语句中有讲:超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现。

什么是策略模式

定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化。

用人话翻译一下就是,运行时我给你这个类的方法传不同的“key”,这个方法会执行不同的业务逻辑。这和 if else 有什么区别吗?

u=3452687011,2726919471&fm=253&fmt=auto&app=138&f=JPEG.png

策略模式做了什么?

其实策略模式的核心思想和if else如出一辙,根据不同的key动态的找到不同的业务逻辑。那它就是如此吗?

实际上,我们口中的策略模式其实就是代码结构上调整,用接口+实现类+分派逻辑来使代码结构可维护性好点。

策略模式的类结构图

c8815cc559bdd15916554f0dee831cde43b004f6.png

  • Strategy(抽象策略):抽象策略类,并且定义策略执行入口
  • ConcreteStrategy(具体策略):实现抽象策略,实现aglorithmInterface 方法
  • Context(环境):运行特定的策略类。

Context上下文,起承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。

public class Context {

     Strategy strategy;

     public Context(Strategy strategy) {
         this.strategy = strategy;
     }

     //上下文接口
     public void contextInterface() {
         strategy.algorithmInterface();
     }
 }

抽象策略类,是对策略、算法的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。

public interface Strategy {
     //算法方法
     void algorithmInterface();
}

策略类的具体实现,抽象策略的实现过多时,就需要考虑使用混合模式来解决策略类膨胀的问题

public class ConcreteStrategyA implements Strategy {
    @Override
    public void algorithmInterface() {
        System.out.println("算法A实现");
    }
}

public class ConcreteStrategyB implements Strategy {
    @Override
    public void algorithmInterface() {
        System.out.println("算法B实现");
    }
}

小结,即使用了策略模式,该写的业务逻辑照常写,到逻辑分派的时候,动态替换策略实现类,来执行相应的逻辑。而它的优化点是抽象出了接口,将业务逻辑封装在一个一个的实现类,任意地替换。在复杂场景(业务逻辑较多)时比直接if else更方便维护扩展。

就几个if else场景,真的需要用到策略模式吗

三四行的业务逻辑,你让我定义一大堆类,有必要这么麻烦吗?看具体的业务逻辑还要点击到不同的类中,有没有简单点的?

策略模式的缺点

其实对于策略模式,让我们觉得麻烦的就是结构比if else复杂了(特别是对新手不“友好”):

  1. 策略实现类会增多
  2. 业务逻辑分散到不同的实现类中,而且没有一个地方可以俯视整个业务逻辑

针对传统策略模式的缺点,我们可以利用Map与Funtion<T,R>接口(Java8的特性)来实现。

代码编写的一般方式:
/**
 * 某个业务服务类
 */
@Service
public class BizService {

    /**
     * 传统的 if else 解决方法
     * 当每个业务逻辑有 3 4 行时,用传统的策略模式不值得,
     * 直接的if else又显得不易读
     */
    public String getCheckResult(String order) {
        if ("校验1".equals(order)) {
            return "执行业务逻辑1";
        } else if ("校验2".equals(order)) {
            return "执行业务逻辑2";
        } else if ("校验3".equals(order)) {
            return "执行业务逻辑3";
        }
        return "不在处理的逻辑中返回业务错误";
    }
}
使用Map+Funtion<T,R>的方式:
/**
 * 某个业务服务类
 */
@Service
public class BizService {

    /**
     * 业务逻辑分派Map
     * Function为函数式接口,下面代码中 Function<String, String> 的含义
     * 是接收一个Stirng类型的变量,返回一个String类型的结果
     */
    private Map<String, Function<String, String>> checkResultDispatcher =
            new HashMap<>();

    /**
     * 初始化 业务逻辑分派Map 其中value 存放的是 lambda表达式
     */
    public void checkResultDispatcherInit() {
        checkResultDispatcher.put("校验1",
                order -> String.format("对%s执行业务逻辑1", order));
        checkResultDispatcher.put("校验2",
                order -> String.format("对%s执行业务逻辑2", order));
        checkResultDispatcher.put("校验3",
                order -> String.format("对%s执行业务逻辑3", order));
    }

    public String getCheckResult(String order) {
        //从逻辑分派Dispatcher中获得业务逻辑代码,result变量是一段lambda表达式
        Function<String, String> result = checkResultDispatcher.get(order);
        if (result != null) {
            //执行这段表达式获得String类型的结果
            return result.apply(order);
        }
        return "不在处理的逻辑中返回业务错误";
    }
}

调用代码

/**
 * 模拟一次http调用
 */
@RestController
public class BizController {

    @Autowired
    private BizService bizService;

    @PostMapping("/checkResult")
    public String test(String order) {
        return bizService.getCheckResult(order);
    }
}

使用Map+Funtion方式之后的好处:

  1. 在一段代码里直观的看到"判断条件"与业务逻辑的映射关系
  2. 不需要单独定义接口与实现类,直接使用现有的函数式接口,而Funtion实现类直接就是业务代码本身。

总结

一、策略模式对代码结构做了什么优化?

抽象了出了接口,将业务逻辑封装成一个一个的实现类,任意地替换。在复杂场景(业务逻辑较多)时比直接 if else 来的好维护些。

二、使用缺点,就是几个if else场景我需要用到策略模式?!

传统接口方式的实现缺点: 1、策略类会很多; 2、业务逻辑分散到各个实现类中,而且没有一个地方可以俯视全局的业务逻辑。

三、Java8的特性帮助解决策略模式的痛点

针对传统策略模式的缺点,可以利用Map与Funtion函数式接口来实现的思路。

参考

新来的同事用策略模式把if else给"优化"了,技术总监说:能不能想好了再改? - 掘金

设计模式最佳套路—— 愉快地使用策略模式

如何用策略模式,优化你代码里的的if-else?

设计模式之策略模式

业务复杂=if else?刚来的大神竟然用策略+工厂彻底干掉了他们!