我们在做项目时,很多情况会根据不同的条件处理不同的逻辑,难免会出现大量的 if-else逻辑判断,条件多的时候,判断分支庞大,就会显得臃肿丑陋。使用设计模式之策略模式,就可以帮我们美化代码。

一、什么是策略模式?

策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。比如每个人都要交个人所得税,但是在美国交个人所得税和在中国交个人所得税就有不同的算税方法。
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。

二、策略模式的组成

策略模式由以下3种角色组成:

  1. 抽象策略角色(Strategy):策略类。通常由一个接口或者抽象类实现。
  2. 具体策略角色(ConcreteStrategy):包装了相关的算法和行为。
  3. 环境角色(Context):持有一个策略类的引用,最终给客户端调用。

三、策略模式的优点

  1. 策略模式的 Strategy 类(或接口等)为 Context 定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能。
  2. 使用策略模式可以避免使用多重条件转移语句。
    多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
  3. 简化了单元测试。
    因为每个算法都有自己的具体的实现类,可以通过自己的接口单独测试。

四、策略模式的缺点

  1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
  2. 策略模式造成很多的策略类,每个具体策略类都会产生一个新类。

五、策略模式的使用场景

  1. 针对同一类型的问题有多种处理方式,仅仅是具体的处理方法有差别时。
  2. 出现同一抽象类有多个子类,而又需要使用 if-else 或者 switch-case 来选择具体子类时。

策略模式一般都是使用在不同策略(不同 if-else 方法体内容)比较复杂或者要执行不同操作等,分别需要大段代码的情况。

六、具体实践

案例:服务端接收浏览器传来的字符串 a、b、operator,operator 值有 add、subtract、multiply 和 divide。服务端根据不同的 operator 值对 a 和 b 做拼接处理并返回。具体实现过程如下所示。
github 源码地址:https://github.com/piaoranyuji/strategy
git 仓库地址:git@github.com:piaoranyuji/strategy.git

6.1 定义策略接口

package com.test.strategy.service;

/**
 * @description 定义策略接口,后面所有策略要实现它
 * @date 2019/12/19 16:00
 */
public interface Strategy {

    String operateStr(String a, String b);
}

6.2 具体策略角色,实现加减乘除处理

package com.test.strategy.service;

/**
 * @description 编写具体策略类,具体策略中实现接口的方法,返回 add 操作结果
 * @date 2019/12/19 16:05
 */
public class AddStrategy implements Strategy {

    @Override
    public String operateStr(String a, String b) {
        return a + "+" + b;
    }
}
package com.test.strategy.service;

/**
 * @description 编写具体策略类,具体策略中实现接口的方法,返回 subtract 操作结果
 * @date 2019/12/19 16:05
 */
public class SubtractStrategy implements Strategy {

    @Override
    public String operateStr(String a, String b) {
        return a + "-" + b;
    }
}
package com.test.strategy.service;

/**
 * @description 编写具体策略类,具体策略中实现接口的方法,返回 multiply 操作结果
 * @date 2019/12/19 16:05
 */
public class MultiplyStrategy implements Strategy {

    @Override
    public String operateStr(String a, String b) {
        return a + "*" + b;
    }
}
package com.test.strategy.service;

/**
 * @description 编写具体策略类,具体策略中实现接口的方法,返回 divide 操作结果
 * @date 2019/12/19 16:05
 */
public class DivideStrategy implements Strategy {

    @Override
    public String operateStr(String a, String b) {
        return a + "/" + b;
    }
}

6.3 定义操作枚举类

package com.test.strategy.bean;

/**
 * @description 定义枚举类,枚举不同的操作规则
 * @date 2019/12/19 19:37
 */
public enum OperatorEnum {

    ADD("com.test.strategy.service.AddStrategy"),
    SUBTRACT("com.test.strategy.service.AddStrategy"),
    MULTIPLY("com.test.strategy.service.AddStrategy"),
    DIVIDE("com.test.strategy.service.DivideStrategy");

    String value;

    OperatorEnum(String value) {
        this.value = value;
    }

    public String getvalue() {
        return this.value;
    }
}

6.4 定义 StrategyFacade 类,明确不同的条件执行不同的策略

package com.test.strategy.service;

import com.test.strategy.bean.OperatorEnum;
import lombok.extern.slf4j.Slf4j;

/**
 * @description 创建工厂方法,创建具体策略类
 * @date 2019/12/19 20:14
 */
@Slf4j
public class StrategyFacade {

    /**
     * 根据 operator 生成实际的操作策略,执行对应的方法
     *
     * @param a        字符串a
     * @param b        字符串b
     * @param operator 操作规则
     * @return 处理后的字符串
     */
    public static String doOperate(String a, String b, String operator) {
        Strategy strategy;

        try {
            strategy = (Strategy) Class.forName(getStrategyType(operator).getvalue()).newInstance();

            return strategy.operateStr(a, b);
        } catch (Exception e) {
            log.error("生成操作策略出现异常", e);

            return "不合法的操作符";
        }
    }

    /**
     * 根据不同 operator 值生成不同的策略(策略场景变化时需要修改)
     *
     * @param operator 操作规则
     * @return OperatorEnum 枚举对象
     */
    private static OperatorEnum getStrategyType(String operator) {
        if ("add".equals(operator)) {
            return OperatorEnum.ADD;
        } else if ("subtract".equals(operator)) {
            return OperatorEnum.SUBTRACT;
        } else if ("multiply".equals(operator)) {
            return OperatorEnum.MULTIPLY;
        } else if ("divide".equals(operator)) {
            return OperatorEnum.DIVIDE;
        } else {
            return null;
        }
    }
}

6.5 测试

3种不同的方法分别对业务需求做处理:

  1. /test1:if-else 方式;
  2. /test2:switch-case 语句;
  3. /test3:策略模式 + 工厂模式。
package com.test.strategy.controller;

import com.test.strategy.service.StrategyFacade;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @description 策略模式测试类
 * @date 2019/12/19 14:41
 */
@RestController
@Slf4j
public class TestController {

    /**
     * if-else 方式实现的两个字符串的不同拼接方法
     *
     * @param a        字符串a
     * @param b        字符串b
     * @param operator 操作规则
     * @return 处理后的字符串
     */
    @RequestMapping(value = "/test1", method = RequestMethod.GET)
    public String test1(String a, String b, String operator) {
        log.info("test1接收参数:a={};b={};operator={}", a, b, operator);

        String result;

        if ("add".equals(operator)) {
            result = a + "+" + b;
        } else if ("subtract".equals(operator)) {
            result = a + "-" + b;
        } else if ("multiply".equals(operator)) {
            result = a + "*" + b;
        } else if ("divide".equals(operator)) {
            result = a + "/" + b;
        } else {
            result = "不合法的操作符";
        }
        return result;
    }

    /**
     * switch 语句实现的两个字符串的不同拼接方法
     *
     * @param a        字符串a
     * @param b        字符串b
     * @param operator 操作规则
     * @return 处理后的字符串
     */
    @RequestMapping(value = "/test2", method = RequestMethod.GET)
    public String test2(String a, String b, String operator) {
        log.info("test2接收参数:a={};b={};operator={}", a, b, operator);

        String result;

        switch (operator) {
            case "add":
                result = a + "+" + b;
                break;
            case "subtract":
                result = a + "-" + b;
                break;
            case "multiply":
                result = a + "*" + b;
                break;
            case "divide":
                result = a + "/" + b;
                break;
            default:
                result = "不合法的操作符";
                break;
        }
        return result;
    }

    /**
     * 策略模式+工厂模式 实现的两个字符串的不同拼接方法
     *
     * @param a        字符串a
     * @param b        字符串b
     * @param operator 操作规则
     * @return 处理后的字符串
     */
    @RequestMapping(value = "/test3", method = RequestMethod.GET)
    public String test3(String a, String b, String operator) {
        log.info("test3接收参数:a={};b={};operator={}", a, b, operator);

        return StrategyFacade.doOperate(a, b, operator);
    }
}
// 测试结果:浏览器输入http://localhost:9999/test3?a=str1&&b=str2&&operator=divide,后端打印结果如下:
// test3接收参数:a=str1;b=str2;operator=divide
// 浏览器获取应答数据:str1/str2