在讲策略设计模式之前,我们先写一个非常简单的计算器功能,我们将以最简单的计算器功能实现来逐渐引出策略设计模式。
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);
}
}
上面代码实现了加减乘除功能,代码看起来直接、简洁,但是该示例代码有几个问题
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方法属于客户端,加减乘除功能是具体的功能实现,用来提供服务,我们不能将服务和客户端代码写到一起,每个类的职责应该更明确,即服务提供方用于专门提供服务,客户端调用方用于调用服务。
我们将具体的功能单独封装到一个类中用于提供服务
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);
}
}
这个示例已经比上一个示例优化的好多了,假如我们需要对计算器增强功能,假如我们再为计算器增加一个求余数的功能,该如何实现呢?
该实现的最大的问题是当我们新增一种运算时除了要新增代码,还需要修改原来的calc代码,这不太好,我们希望新增运算功能时只需要新增代码,不要修改原来已有的代码。
为了达到新增运算方式不修改代码只增加代码的方式,我们需要将每个预算继续抽象,我们新增一个计算的接口,并将加减乘除求余数分别封装到每个具体的实现类里面。
/**
* 计算策略.
*
*/
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);
}
}
上下文只持有一个运算接口的引用并提供一个执行策略的方法,这里的方法实现也很简单,就是简单调用具体运算实现类的方法
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
上下文中的方法,上下文中的方法也是简单的调用具体的实现类,在这里感觉上下文没太大的意义。
现在加入我们要实现一个促销的功能,假如目前支持 满x元减y元、第x件y折、满x件y折等多种促销方式,假如业务规定当满足多种促销时取所有促销的最低价。要想实现这种功能,我们首先找出当前订单能够享受的促销类型,然后分别计算每种促销类型促销后的促销价,然后比较所有促销类型对应的促销价,取最低的促销价格。
上下文中大部分情况下是直接调用调用接口的方法,但是也有一些情况是需要在上下文中处理一些逻辑,处理不同实现的依赖关系。
/**
* 促销上下文
* @author Mengday Zhang
* @version 1.0
* @since 2019-08-16
*/
public class PromotionContext {
public BigDecimal executeStrategy() {
BigDecimal promotionPrice = null;
List<PromotionStrategy> promotionList = ...;
for (PromotionStrategy strategy : promotionList) {
// 执行下次运算需要将上次运算的结果作为下次运算的参数,
// 每次计算的结果需要和传进来的上次运算结果做比较,取最小值作为新的结果返回
promotionPrice = strategy.executeStrategy(promotionPrice);
}
return promotionPrice;
}
}
到此为止,策略模式的所有元素都体现出来了,没错示例5就是策略模式的具体实现。
上个示例我们看到仍然有很多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);
}
}
我们将每个具体的实现类变成单例模式,这里通过懒汉模式来实现单例类。
/**
* 加法操作
*/
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);
}
}