一、概述
策略模式,英文全称是 Strategy Design Pattern。在 GoF 的《设计模式》一书中,它是这样定义的:定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。
工厂模式是解耦对象的创建和使用,观察者模式是解耦观察者和被观察者。策略模式是将定义、创建、使用这三部分进行解耦。
二、定义、创建、使用
2.1 定义
/**
* 策略接口
*/
public interface Strategy {
void executeStrategy();
}
/**
* 策略枚举
*/
@Getter
public enum StrategyEnum {
A("A", "A策略模式"),
B("B", "B策略模式");
private String type;
private String desc;
StrategyEnum(String type, String desc) {
this.type = type;
this.desc = desc;
}
}
/**
* A策略模式
*/
@Slf4j
public class StrategyA implements Strategy {
@Override
public void executeStrategy() {
log.info("执行策略A!");
}
}
/**
* B策略模式
*/
@Slf4j
public class StrategyB implements Strategy {
@Override
public void executeStrategy() {
log.info("执行策略B!");
}
}
2.2 创建
/**
* 策略工厂
*/
public class StrategyFactory {
/**
* 管理所有策略的Map
*/
private static final Map<StrategyEnum, Strategy> STRATEGY_MAP = new HashMap<>(2);
/**
* 静态代码块,启动的时候将所有策略的实现加载进去
*/
static {
STRATEGY_MAP.put(StrategyEnum.A, new StrategyA());
STRATEGY_MAP.put(StrategyEnum.B, new StrategyB());
}
/**
* 对外只提供获取策略的方法
*/
public Strategy getStrategy(StrategyEnum strategyEnum) {
if (null == strategyEnum) {
throw new IllegalArgumentException("This strategyEnum is not exist!");
}
return STRATEGY_MAP.get(strategyEnum);
}
}
2.3 使用
/**
* 策略使用
*/
@RunWith(JUnit4.class)
public class StrategyTest {
/**
* 测试策略的使用
*/
@Test
public void testGetStrategy() {
StrategyFactory.getStrategy(StrategyEnum.A);
strategy.executeStrategy();
}
}
这种属于【有状态】的策略模式,当然还有无状态的策略模式。
三、Spring策略模式的应用
使用spring的时候,我们可以使用@PostConstruct
注解,在spring加载完毕对象后,进行后置处理的时候,将实现类放入map中。定义部分相同,其他有所改动。
3.1 创建
/**
* spring的策略管理
*/
@Component
public class StrategySpringFactory {
/**
* 管理所有策略的Map
*/
private Map<StrategyEnum, Strategy> strategyMap = new HashMap<>(2);
/**
* 将策略注入
*/
@Resource
private Strategy strategyA;
@Resource
private Strategy strategyB;
/**
* 初始化:后置处理器
*/
@PostConstruct
public void init() {
strategyMap.put(StrategyEnum.A, strategyA);
strategyMap.put(StrategyEnum.B, strategyB);
}
/**
* 对外只提供获取策略的方法
*/
public Strategy getStrategy(StrategyEnum strategyEnum) {
if (null == strategyEnum) {
throw new IllegalArgumentException("This strategyEnum is not exist!");
}
return strategyMap.get(strategyEnum);
}
}
3.2 使用
/**
* 策略使用
*/
@SpringBootTest(classes = AppStart.class)
@RunWith(SpringRunner.class)
public class StrategyTest {
/**
* 注入工厂
*/
@Resource
private StrategySpringFactory strategySpringFactory;
/**
* 策略模式使用
*/
@Test
public void testGetStrategySpring() {
Strategy strategy = strategySpringFactory.getStrategy(StrategyEnum.B);
strategy.executeStrategy();
}
}
四、策略模式+反射的实际案例
场景描述 :有一个活动(这里设置为招收活动),会有很多的规则,任何人都可以报名,但是必须满足条件,为了方便未来增加新的活动,代码好维护,我们需要使用枚举作为策略,同时使用反射统一化代码。
案例代码如下(省略set.get):
/*
* Person类,父类,任何人,具备基础属性
*/
public class Person {
/*姓名*/
private String name;
/*年龄*/
private Integer age;
/*失信率*/
private Double breakRate;
}
/*
* teacher类,后面可以根据需求继续增加属性或者职业
*/
public class Teacher extends Person {
/*职级*/
private Integer level;
}
/*
* student类,后面可以根据需求继续增加属性或者职业
*/
public class Student extends Person {
/*分数*/
private Double score;
}
/*
* 活动类,具备规则属性,即招收要求,根据枚举去找对应的比较方式
*/
public class EmployRule {
/*规则名称*/
private String ruleName;
/*规则标识*/
private String ruleProperty;
/*规则值*/
private String ruleValue;
}
/*
* 规则枚举,其中还有枚举,分别是比较类型,以及值类型
*/
public enum EmployRuleEnum {
MIN_AGE("最小年龄", "age", RuleTypeEnum.GREATER_EQUAL, DataTypeEnum.INT),
MIN_LEVEL("最小职级", "level", RuleTypeEnum.GREATER_EQUAL, DataTypeEnum.INT),
MIN_SCORE("最低分数", "score", RuleTypeEnum.GREATER_EQUAL, DataTypeEnum.DOUBLE),
MAX_BREAK_RATE("最高失信率", "breakRate", RuleTypeEnum.LESS_EQUAL, DataTypeEnum.DOUBLE);
/*规则名称*/
private String ruleName;
/*规则标识*/
private String ruleProperty;
/*规则类型(大于,小于,等于)*/
private RuleTypeEnum ruleType;
/*值类型(整数、浮点)*/
private DataTypeEnum dataType;
/**
* 根据property获取枚举类
* @param property
* @return
*/
public static EmployRuleEnum getByProperty(String property){
if(StringUtils.isEmpty(property)){
return null;
}
for(EmployRuleEnum ruleEnum : EmployRuleEnum.values()){
if(property.equals(ruleEnum.getRuleProperty())){
return ruleEnum;
}
}
return null;
}
EmployRuleEnum(String ruleName, String ruleProperty, RuleTypeEnum ruleType, DataTypeEnum dataType) {
this.ruleName = ruleName;
this.ruleProperty = ruleProperty;
this.ruleType = ruleType;
this.dataType = dataType;
}
}
/*
* 比较类型
*/
public enum RuleTypeEnum {
/*大于等于*/
GREATER_EQUAL(1, "大于等于"),
/*小于等于*/
LESS_EQUAL(2, "小于等于")
;
/*类型*/
private int type;
/*描述*/
private String desc;
/**
* 根据type获取枚举
* @param type
* @return
*/
public static RuleTypeEnum getByType(Integer type){
if(type == null){
return null;
}
for(RuleTypeEnum typeEnum : RuleTypeEnum.values()){
if(typeEnum.type == type){
return typeEnum;
}
}
return null;
}
RuleTypeEnum(int type, String desc) {
this.type = type;
this.desc = desc;
}
}
/*
* 值类型
*/
public enum DataTypeEnum {
INT(1, "整数") {
@Override
public int compare(String value, Object o) throws Exception {
if (!(o instanceof Integer)) {
throw new Exception();
}
Integer obj = (Integer) o;
return Integer.valueOf(value).compareTo(obj); //规则中获取的值-用户传来的数据值
}
},
LONG(2, "长整型") {
@Override
public int compare(String value, Object o) throws Exception {
if (!(o instanceof Long)) {
throw new Exception();
}
Long obj = (Long) o;
return Long.valueOf(value).compareTo(obj);
}
},
FLOAT(3, "浮点型") {
@Override
public int compare(String value, Object o) throws Exception {
if (!(o instanceof Float)) {
throw new Exception();
}
Float obj = (Float) o;
return Float.valueOf(value).compareTo(obj);
}
},
DOUBLE(4, "长浮点型") {
@Override
public int compare(String value, Object o) throws Exception {
if (!(o instanceof Double)) {
throw new Exception();
}
Double obj = (Double) o;
return Double.valueOf(value).compareTo(obj);
}
};
/**
* 类型标识
*/
private Integer type;
/**
* 描述
*/
private String desc;
/**
* 根据type查询枚举类
* @param type
* @return
*/
public static DataTypeEnum getByDataType(Integer type){
if(type == null){
return null;
}
for(DataTypeEnum dataTypeEnum : DataTypeEnum.values()){
if(dataTypeEnum.getType().equals(type)){
return dataTypeEnum;
}
}
return null;
}
/**
* 比较方法,用于枚举方法重写
* @param value
* @param o
* @return
* @throws Exception
*/
public abstract int compare(String value, Object o)throws Exception;
DataTypeEnum(Integer type, String desc) {
this.type = type;
this.desc = desc;
}
}
/**
* service方法实现
*/
public class VerifyServiceImpl implements VerifyService {
/*json*/
private static final ObjectMapper objectMaper = new ObjectMapper();
/*日志*/
private static Logger logger = Logger.getLogger(VerifyServiceImpl.class.getName());
@Override
public void doVerify(Person person, EmployRule rule) throws Exception {
logger.info(String.format("VerifyServiceImpl.doVerify 入参:person = %s, rule = %s", objectMaper.writeValueAsString(person), objectMaper.writeValueAsString(rule)));
//校验参数
String ruleName = rule.getRuleName();//规则名称
String ruleProperty = rule.getRuleProperty();//规则标识
String ruleValue = rule.getRuleValue();//规则值
if (null == person || StringUtils.isEmpty(ruleProperty)
|| StringUtils.isEmpty(ruleValue) || StringUtils.isEmpty(ruleName)) {
throw new Exception("入参有空值");
}
//获取值类型的枚举类
EmployRuleEnum property = EmployRuleEnum.getByProperty(ruleProperty);
if (null == property) {
throw new Exception("数据类型不存在");
}
DataTypeEnum dataType = property.getDataType();
//获取规则类型:大于,小于,等于
RuleTypeEnum ruleTypeEnum = property.getRuleType();
if (null != ruleTypeEnum) {
switch (ruleTypeEnum) {
//小于等于
case LESS_EQUAL:
if (this.compare(person, ruleProperty, dataType, ruleValue) <= 0) {
throw new Exception(String.format("%s 不符合要求", ruleName));
}
return;
//大于等于
case GREATER_EQUAL:
if (this.compare(person, ruleProperty, dataType, ruleValue) >= 0) {
throw new Exception(String.format("%s 不符合要求", ruleName));
}
return;
default:
throw new Exception("未知规则类型");
}
}
}
/**
* 比较的共用方法,反射实现
* @param person 传入的的获取,可以获取对应属性的值
* @param ruleProperty 规则属性标识
* @param valueType 值类型(int, long, float, double)的枚举
* @param ruleValue 规则的值
* @return 比较结果
* @throws Exception
*/
private int compare(Person person, String ruleProperty, DataTypeEnum valueType, String ruleValue) throws Exception {
/*反射处理*/
Class<? extends Person> clazz = person.getClass();
try {
//根据名称获取某个变量的值(私有)
Field field = null;
/*Person是父类,传入的可能是其子类,这里分开处理*/
//子类中获取(私有)
try {
field = clazz.getDeclaredField(ruleProperty);
} catch (Exception e) {
}
//父类中获取(私有)
if (null == field) {
try {
field = clazz.getSuperclass().getDeclaredField(ruleProperty);
} catch (Exception e) {
}
}
//设置私有变量访问权限
if (null == field) {
throw new Exception("获取到的规则值为空");
}
field.setAccessible(true);
//返回指定对象上该field字段对应的值
Object o = field.get(person);
if (null == o) {
throw new Exception("获取不到对象中实际的值");
}
//调用数据类型枚举的比较方法进行比较(规则值-对象值)
return valueType.compare(ruleValue, o);
} catch (Exception e) {
logger.info(String.format("compare 比较方法反射获取不到属性, ruleProperty = %s, exceptionMessage = %s",ruleProperty , e.toString()));
throw new Exception("反射方法异常,获取不到值!");
}
}
}
也可以使用java8的函数式接口Comparator接口
public enum DataTypeEnum {
INT(1, "整型"){
@Override
public int compare(String ruleValue, Object o) {
Integer value = (Integer)o;
Comparator<Integer> comparator = Integer::compareTo;
return comparator.compare(Integer.valueOf(ruleValue), value);
}
},
DOUBLE(2, "浮点") {
@Override
public int compare(String ruleValue, Object o) {
Double value = (Double)o;
Comparator<Double> comparator = Double::compareTo;
return comparator.compare(Double.valueOf(ruleValue), value);
}
}
;
public abstract int compare(String ruleValue, Object o);
private Integer type;
private String desc;
DataTypeEnum(Integer type, String desc) {
this.type = type;
this.desc = desc;
}
public Integer getType() {
return type;
}
public String getDesc() {
return desc;
}
public static DataTypeEnum getDataTypeEnumByType(Integer type){
if (null == type) {
return null;
}
for (DataTypeEnum bean : DataTypeEnum.values()) {
if (type.equals(bean.getType())) {
return bean;
}
}
return null;
}
}
@Test
public void testIDoNotKnowWhatYouWantToTest() {
Integer dataType = 2;
DataTypeEnum dataTypeEnum = DataTypeEnum.getDataTypeEnumByType(dataType);
int compare;
switch (dataTypeEnum) {
case INT:
compare = dataTypeEnum.compare("20", 50);
System.out.println(compare);
break;
case DOUBLE:
compare = dataTypeEnum.compare("7.5",7.5);
System.out.println(compare);
default:
break;
}
}