策略设计模式(Strategy Pattern)

在讲策略设计模式之前,我们先写一个非常简单的计算器功能,我们将以最简单的计算器功能实现来逐渐引出策略设计模式。

1. 最简单的计算器实现(示例1)

public class Main {
    public static void main(String[] args) {
        // 计算类型:1:加法 2:减法 3:乘法 4:除法
        // 计算类型和计算的值都是正常客户端传过来的
        int calcType = 1;
        int num1 = 1;
        int num2 = 3;

		// 计算器真正的实现
        int result = 0;
        if (calcType == 1) {
            result = num1 + num2;
        } else if (calcType == 2) {
            result = num1 - num2;
        } else if (calcType == 3) {
            result = num1 * num2;
        } else if (calcType == 4) {
            result = num1 / num2;
        }

        System.out.println(result);
    }
}

上面代码实现了加减乘除功能,代码看起来直接简洁,但是该示例代码有几个问题

  1. 计算类型使用了魔法值(就是写死的值),程序中应该避免直接使用魔法值,可以使用枚举类来优化;
  2. 计算器功能的每种操作非常简单,只需要对两个数字运算一下就完了,每个操作也就是一行代码的事,但实际开发过程中每种业务处理可能要几十行甚至更多行代码来完成,现在是4种类型,如果每种类型的业务需要100行代码,这四种就会有400行代码,这只是if else的代码量再加上该方法的其它代码可以想象该方法的代码量很大,我们需要控制方法的行数,一般通常控制到100行以内,我们可以通过将每个运算的具体实现都提取成一个单独的方法,这样一来就缩小每个if else的代码的行数,只需要一行代码,只需要调用一个方法就可以了。

2. 枚举类+提取方法实现(示例2)

  • 增加一个枚举类CalcTypeEnum用于表示所有的运算类型, 使用枚举类来优化魔法值
  • 将每种具体的运算实现提取为独立的方法
public enum CalcTypeEnum {
    ADD(1, "加法操作"),
    SUB(2, "减法操作"),
    MUL(3, "乘法操作"),
    DIV(4, "除法操作"),
    ;

    private Integer code;
    private String description;

    CalcTypeEnum(Integer code, String description) {
        this.code = code;
        this.description = description;
    }

    public Integer getCode() {
        return code;
    }

    public String getDescription() {
        return description;
    }
}
public class Main {
    public static void main(String[] args) {
        // 计算类型:1:加法 2:减法 3:乘法 4:除法
        int calcType = 1;

        int num1 = 1;
        int num2 = 3;
        int result = 0;

		// 使用枚举来代替魔法值
        if (CalcTypeEnum.ADD.getCode().equals(calcType)) {
        	// 假如加法运算中还包含其它业务逻辑,那么这些逻辑也被封装到方法中了,此处只有一行的代码量
            result = add(num1, num2);
        } else if (CalcTypeEnum.SUB.getCode().equals(calcType)) {
            result = sub(num1, num2);
        } else if (CalcTypeEnum.MUL.getCode().equals(calcType)) {
            result = mul(num1, num2);
        } else if (CalcTypeEnum.DIV.getCode().equals(calcType)) {
            result = div(num1, num2);
        }

        System.out.println(result);
    }

	/** 将具体的运算实现代码封装成独立的方法 */
    public static int add(int num1, int num2) {
        System.out.println("加法运算其它业务逻辑start");
        System.out.println("此处省略几十行代码...");
        System.out.println("加法运算其它业务逻辑end");
        return num1 + num2;
    }

    public static int sub(int num1, int num2) {
        return num1 - num2;
    }

    public static int mul(int num1, int num2) {
        return num1 * num2;
    }

    public static int div(int num1, int num2) {
        return num1 / num2;
    }
}

该示例解决了上个示例的缺点,但该示例仍有问题:

main方法属于客户端,加减乘除功能是具体的功能实现,用来提供服务,我们不能将服务和客户端代码写到一起,每个类的职责应该更明确,即服务提供方用于专门提供服务,客户端调用方用于调用服务。

3. 计算器功能单独封装成类(示例3)

我们将具体的功能单独封装到一个类中用于提供服务

public class Calculator {

    public int calc(CalcTypeEnum calcType, int num1, int num2) {
        int result = 0;

        if (CalcTypeEnum.ADD.equals(calcType)) {
            result = add(num1, num2);
        } else if (CalcTypeEnum.SUB.equals(calcType)) {
            result = sub(num1, num2);
        } else if (CalcTypeEnum.MUL.equals(calcType)) {
            result = mul(num1, num2);
        } else if (CalcTypeEnum.DIV.equals(calcType)) {
            result = div(num1, num2);
        }

        return result;
    }

    public int add(int num1, int num2) {
    	System.out.println("加法运算其它业务逻辑start");
        System.out.println("此处省略几十行代码...");
        System.out.println("加法运算其它业务逻辑end");
        return num1 + num2;
    }

    public int sub(int num1, int num2) {
        return num1 - num2;
    }

    public int mul(int num1, int num2) {
        return num1 * num2;
    }

    public int div(int num1, int num2) {
        return num1 / num2;
    }

}

因为服务方单独封装成一个独立的类,所以服务调用方代码就减少了很多,显得很清爽。

public class Main {
    public static void main(String[] args) {
        int num1 = 1;
        int num2 = 3;

        Calculator calculator = new Calculator();
        int result = calculator.calc(CalcTypeEnum.ADD, num1, num2);

        System.out.println(result);
    }
}

这个示例已经比上一个示例优化的好多了,假如我们需要对计算器增强功能,假如我们再为计算器增加一个求余数的功能,该如何实现呢?

  1. 枚举类CalcTypeEnum增加求余数的枚举值
  2. 修改Calculator类,增加一个求余数的方法,然后在calc方法中再增加一个else if 块

该实现的最大的问题是当我们新增一种运算时除了要新增代码,还需要修改原来的calc代码,这不太好,我们希望新增运算功能时只需要新增代码,不要修改原来已有的代码。

4.将具体运算分别封装成单独的类中(示例4)

为了达到新增运算方式不修改代码只增加代码的方式,我们需要将每个预算继续抽象,我们新增一个计算的接口,并将加减乘除求余数分别封装到每个具体的实现类里面。

/**
 * 计算策略.
 *
 */
public interface CalcStrategy {

    int calc(int num1, int num2);
}
/**
 * 加法操作
 */
public class AddStrategy implements CalcStrategy {
    @Override
    public int calc(int num1, int num2) {
    	System.out.println("加法运算其它业务逻辑start");
        System.out.println("此处省略几十行代码...");
        System.out.println("加法运算其它业务逻辑end");
        return num1 + num2;
    }
}
/**
 * 减法操作.
 */
public class SubStrategy implements CalcStrategy {
    @Override
    public int calc(int num1, int num2) {
        return num1 - num2;
    }
}
/**
 * 乘法操作
 */
public class MulStrategy implements CalcStrategy {
    @Override
    public int calc(int num1, int num2) {
        return num1 * num2;
    }
}
/**
 * 除法操作
 */
public class DivStrategy implements CalcStrategy {
    @Override
    public int calc(int num1, int num2) {
        return num1 / num2;
    }
}

我们增加一个求余数的运算只需要增加一个枚举值并新增一个求余的实现类, 这样我们就实现了新增一个功能只需要新增代码而不用修改之前的代码的目的。

public enum CalcTypeEnum {
    ADD(1, "加法操作"),
    SUB(2, "减法操作"),
    MUL(3, "乘法操作"),
    DIV(4, "除法操作"),
    // 求余数运算
    REM(5, "求余操作"),
    ;
}
/**
 * 求余操作
 */
public class RemStrategy implements CalcStrategy {
    @Override
    public int calc(int num1, int num2) {
        return num1 % num2;
    }
}
public class Main {
	// 用户要计算的类型
    private static final int CALC_TYPE = 5;

    public static void main(String[] args) {
    	// 根据用户要运算的类型调用相应实现类的方法
        CalcStrategy calcStrategy = null;
        if (CalcTypeEnum.ADD.getCode().equals(CALC_TYPE)) {
            calcStrategy = new AddStrategy();
        } else if (CalcTypeEnum.SUB.getCode().equals(CALC_TYPE)) {
            calcStrategy = new SubStrategy();
        } else if (CalcTypeEnum.MUL.getCode().equals(CALC_TYPE)) {
            calcStrategy = new MulStrategy();
        } else if (CalcTypeEnum.DIV.getCode().equals(CALC_TYPE)) {
            calcStrategy = new DivStrategy();
        } else if (CalcTypeEnum.REM.getCode().equals(CALC_TYPE)) {
            calcStrategy = new RemStrategy();
        }

        int result = calcStrategy.calc(10, 20);
        System.out.println(result);
    }
}

5. 为示例4引入上下文(示例5)

上下文只持有一个运算接口的引用并提供一个执行策略的方法,这里的方法实现也很简单,就是简单调用具体运算实现类的方法

public class CalcStrategyContext {

	// 运算接口
    private CalcStrategy strategy;

	// 通过构造函数或者set方法赋值
    public CalcStrategyContext(CalcStrategy strategy) {
        this.strategy = strategy;
    }

    public int executeStrategy(int a, int b) {
    	// 简单调用具体实现对应的方法
        return strategy.calc(a, b);
    }

    public CalcStrategy getStrategy() {
        return strategy;
    }
    public void setStrategy(CalcStrategy strategy) {
        this.strategy = strategy;
    }
}

public class Main {

    private static final Integer CALC_TYPE = 1;

    public static void main(String[] args) {

        CalcStrategy calcStrategy = null;
        if (CalcTypeEnum.ADD.getCode().equals(CALC_TYPE)) {
            calcStrategy = new AddStrategy();
        } else if (CalcTypeEnum.SUB.getCode().equals(CALC_TYPE)) {
            calcStrategy = new SubStrategy();
        } else if (CalcTypeEnum.MUL.getCode().equals(CALC_TYPE)) {
            calcStrategy = new MulStrategy();
        } else if (CalcTypeEnum.DIV.getCode().equals(CALC_TYPE)) {
            calcStrategy = new DivStrategy();
        }

		// 这里不再直接调用接口方法了,而是调用上下文类中的方法
		// 上下文就是对接口的一种简单的装饰和封装
        CalcStrategyContext context = new CalcStrategyContext(calcStrategy);
        int result = context.executeStrategy(20, 30);

        System.out.println(result);
    }
}

此示例和上个示例不同的就是调用方法的对象不同,一个是直接调用CalcStrategy#calc接口的方法,一个是调用CalcStrategyContext#executeStrategy上下文中的方法,上下文中的方法也是简单的调用具体的实现类,在这里感觉上下文没太大的意义,感觉是多此一举,完全是画蛇添足。

5.2 上下文存在的意义是什么?

执行策略方法最重要的是直接调用具体策略实现类中的方法来完成功能,但是如果想在执行每个策略模式前或者在调用每个策略模式后都额外执行一段逻辑时
* 此时就可以在执行策略方法中增加相应的逻辑。策略上下文的目的就是为了以后的扩展性,为了便于统一在执行策略模式时执行一些逻辑而存在的。

public int executeStrategy(int a, int b) {
	// 简单调用具体实现对应的方法
    System.out.println("策略模式开始执行");
    int result = strategy.calc(a, b);
    System.out.println("侧罗模式执行结束");
    return result;
}

到此为止,策略模式的所有元素都体现出来了,没错示例5就是策略模式的具体实现。

策略设计模式的特点

  1. 提供一个策略接口
  2. 提供多个策略接口的实现类
  3. 提供一个策略上下文

在这里插入图片描述

策略模式优点

  • 可以自由切换算法(具体实现)
  • 避免了多条件的判断(干掉了if else)
  • 扩展性好可以定义新的算法提供给使用者(增加新功能时只需要增加代码而不需要修改代码)

策略模式缺点

  • 算法类数量增多,每个算法都是一个类,这对于初级程序员比较难以接受

6. 通过枚举类撤掉干掉if else(示例6)

上个示例我们看到仍然有很多if else代码,我们需要减少甚至消灭这种代码,消灭if else这里列举两种方式
, 一种是通过配置枚举类。在枚举中增加对应的运算实现类,并提供一个根据code来获取对应的枚举类的方法, 获取到枚举类了就获取到对应的实现类了。

public enum CalcTypeEnum {
	 // code 一般设置为具体实现类的前缀
    ADD("Add", "加法操作", new AddStrategy()),
    SUB("Sub", "减法操作", new SubStrategy()),
    MUL("Mul", "乘法操作", new MulStrategy()),
    DIV("Div", "除法操作", new DivStrategy()),
    ;

    private String code;
    private String description;
    private CalcStrategy calcStrategy;

    CalcTypeEnum(String code, String description, CalcStrategy calcStrategy) {
        this.code = code;
        this.description = description;
        this.calcStrategy = calcStrategy;
    }

	// 根据code获取对应的枚举类型
    public static CalcTypeEnum getCalcTypeEnum(String code) {
        for (CalcTypeEnum calcTypeEnum : CalcTypeEnum.values()) {
            if (calcTypeEnum.getCode().equals(code)) {
                return calcTypeEnum;
            }
        }

        return null;
    }

    public String getCode() {
        return code;
    }

    public String getDescription() {
        return description;
    }

    public CalcStrategy getCalcStrategy() {
        return calcStrategy;
    }
}
public class Main {
    private static final String CALC_TYPE = "Sub";

    public static void main(String[] args) {

        // 消除if else,根据code获取到对应的枚举类,进而获取到对应的计算实现类
        CalcStrategy addStrategy = CalcTypeEnum.getCalcTypeEnum(CALC_TYPE).getCalcStrategy();
        CalcStrategyContext context = new CalcStrategyContext(addStrategy);
        int result = context.executeStrategy(20, 30);

        System.out.println(result);
    }
}

7. 通过反射彻底干掉if else(方式二)

我们将每个具体的实现类变成单例模式,这里通过懒汉模式来实现单例类。

/**
 * 加法操作
 */
public class AddStrategy implements CalcStrategy {

    private static AddStrategy addStrategy;

    private AddStrategy() {

    }

	// 懒汉模式实现单例类
    public static AddStrategy getInstance() {
        if (addStrategy == null) {
            addStrategy = new AddStrategy();
        }

        return addStrategy;
    }

    @Override
    public int calc(int num1, int num2) {
    	System.out.println("加法运算其它业务逻辑start");
        System.out.println("此处省略几十行代码...");
        System.out.println("加法运算其它业务逻辑end");
        return num1 + num2;
    }
}

通过反射获取某个具体的类,然后调用具体类的getInstance方法,从而获取对应的运算实现类。

public class CalcStrategyUtils {

    public static CalcStrategy getCalcStrategy(String calcType) {
        try {
        	// 这里包名是写死的,开发时需要将实现类统一放到该包下面,类的命名也是有规则的,以Strategy作为后缀
            String path = "com.example.design.strategy.demo7." + calcType.concat("Strategy");
            Class<?> clazz = Class.forName(path);

            CalcStrategy instance = (CalcStrategy) clazz.getDeclaredMethod("getInstance").invoke(null, null);
            return instance;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Load [" + calcType.concat("Strategy") + "] Error :", e);
        }
    }
}
public class Main {

    private static final String CALC_TYPE = "Add";

    public static void main(String[] args) {

        // 通过反射获取到具体实现类型,从而消除if else
        CalcStrategy addStrategy = CalcStrategyUtils.getCalcStrategy(CALC_TYPE);
        CalcStrategyContext context = new CalcStrategyContext(addStrategy);

        int result = context.executeStrategy(20, 30);

        System.out.println(result);
    }
}
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风流 少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值