今天总结一下策略模式使用过程中获取算法族(行为)的方式,内容纯属个人观点,欢迎指正。
一、策略模式的场景及难点
1、策略模式的定义及说明在此就不提了,没有基础的推荐大家一本书《Head First 设计模式》,图文及场景的选择让读者很容易理解。
个人的使用场景:
项目中有多个执行器,每个执行器执行不同的业务处理,希望将这些执行器做统一的入口,根据入口的行为参数决定使用哪个执行器去执行相应的任务,在策略模式的使用中,个人觉得这也是难点所在,想到的做法有三种,通过Spring getBean、Java的反射以及另一种在getBean的基础上增加Spring Annotation,下面逐一介绍三种方式的使用。
二、策略模式获取处理类的方式
1、Spring getBean
a.执行器的入口
/**
* @author xiaokaige
*/
@RestController
@RequestMapping(value = "/test")
public class TestController {
@Autowired
private ExecutorStrategyFactory strategyFactory;
@GetMapping("strategy")
public void strategy(){
//创建具体的执行策略,并执行为
ExecutorStrategyInterface strategy = strategyFactory.createStrategy("executorB");
strategy.doExecutorAction();
}
}
b.创建具体的执行策略
1、通过@PostConstruct修饰init(),在服务器加载Servle的时候运行,并且只会被服务器执行一次。
2、通过applicationContext.getBeansOfType(ExecutorStrategyInterface.class)获取所有实现ExecutorStrategyInterface的Bean,将其放入内存EXECUTOR_BEANS。
3、遍历实现ExecutorStrategyInterface的Bean并返回与参数对应的实例。
/**
* @author xiaokaige
*/
@Component
public class ExecutorStrategyFactory {
private static final Map<String,ExecutorStrategyInterface> EXECUTOR_BEANS = Maps.newConcurrentMap();
@Autowired
private ApplicationContext applicationContext;
/**
* 根据不同的执行器创建不同的策略
* @return
*/
public ExecutorStrategyInterface createStrategy(String executor) {
Optional<ExecutorStrategyInterface> strategyOptional =
EXECUTOR_BEANS
.entrySet()
.stream()
.map(e -> {
if (Objects.equals(e.getKey(),executor)) {
return e.getValue();
}
return null;
}).filter(Objects::nonNull).findAny();
if(strategyOptional.isPresent()){
System.out.println(strategyOptional.get());
return strategyOptional.get();
}
throw new RuntimeException("策略获得失败");
}
/**
* 初始化策略列表
*/
@PostConstruct
private void init() {
EXECUTOR_BEANS.putAll(applicationContext.getBeansOfType(ExecutorStrategyInterface.class));
}
}
c.执行器接口
/**
* @author xiaokaige
*/
public interface ExecutorStrategyInterface {
/**
* 执行器接口
*/
void doExecutorAction();
}
d.执行器接口的实现
/**
* @author xiaokaige
*/
@Slf4j
@Service
public class ExecutorA implements ExecutorStrategyInterface {
@Override
public void doExecutorAction() {
log.info("I entered through Strategy A.");
}
}
/**
* @author xiaokaige
*/
@Slf4j
@Service
public class ExecutorB implements ExecutorStrategyInterface {
@Override
public void doExecutorAction() {
log.info("I entered through Strategy B.");
}
}
e.请求接口
请求接口可以看到控制台输出:I entered through Strategy B.
补充一点,此方式也可使用@Service("别名"),此时applicationContext.getBeansOfType(ExecutorStrategyInterface.class)拿到的是“别名”
2、Java反射
这种方式非常简单,不需要策略工厂类,直接在调用的时候通过全类名及方法名利用java反射调用方法就好了。
/**
* @author xiaokaige
*/
@RestController
@RequestMapping(value = "/test")
public class TestController {
@GetMapping("strategy")
public void strategy(){
try {
Class c = Class.forName("com.xiaokaige.demo.strategy.ExecutorB");
Method m = c.getMethod("doExecutorAction");
m.invoke(c.newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
}
控制台输出结果如下:
3、Spring Annotation
a.定义 @Annotation
首先通过@interface Annotation{ } 定义一个注解 @Annotation
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExecutorStrategyAnnotation {
/**
* 执行器id,默认值1
*/
int executorId() default 1;
}
b.为实现ExecutorStrategyInterface接口的类添加一个Id
/**
* @author xiaokaige
*/
@Slf4j
@Service
@ExecutorStrategyAnnotation(executorId = ExecutorStrategyConstants.A)
public class ExecutorA implements ExecutorStrategyInterface {
@Override
public void doExecutorAction() {
log.info("I entered through Strategy A.");
}
}
/**
* @author xiaokaige
*/
@Slf4j
@Service
@ExecutorStrategyAnnotation(executorId = ExecutorStrategyConstants.B)
public class ExecutorB implements ExecutorStrategyInterface {
@Override
public void doExecutorAction() {
log.info("I entered through Strategy B.");
}
}
/**
* @author xiaokaige
*/
public class ExecutorStrategyConstants {
public static final int B = 2;
public static final int A = 3;
public static final int C =4;
}
c.执行器的入口
这里创建策略的参数有执行器的名称换为策略的ID
/**
* @author xiaokaige
*/
@RestController
@RequestMapping(value = "/test")
public class TestController {
@Autowired
private ExecutorStrategyFactory strategyFactory;
@GetMapping("strategy")
public void strategy(){
//创建具体的执行策略,并执行为
ExecutorStrategyInterface strategy = strategyFactory.createStrategy(3);
strategy.doExecutorAction();
}
}
d.策略工厂获实现
ExecutorStrategyInterface的每个Bean的Id,返回与传参对应的策略
/**
* @author xiaokaige
*/
@Component
public class ExecutorStrategyFactory {
private static final Map<String,ExecutorStrategyInterface> EXECUTOR_BEANS = Maps.newConcurrentMap();
@Autowired
private ApplicationContext applicationContext;
/**
* 根据不同的执行器创建不同的策略
* @return
*/
public ExecutorStrategyInterface createStrategy(int executor) {
Optional<ExecutorStrategyInterface> strategyOptional =
EXECUTOR_BEANS
.entrySet()
.stream()
.map(e -> {
ExecutorStrategyAnnotation validExecutor = e.getValue().getClass().getDeclaredAnnotation(ExecutorStrategyAnnotation.class);
if (Objects.equals(validExecutor.executorId(),executor)) {
return e.getValue();
}
return null;
}).filter(Objects::nonNull).findFirst();
if(strategyOptional.isPresent()){
System.out.println(strategyOptional.get());
return strategyOptional.get();
}
throw new RuntimeException("策略获得失败");
}
/**
* 初始化策略列表
*/
@PostConstruct
private void init() {
EXECUTOR_BEANS.putAll(applicationContext.getBeansOfType(ExecutorStrategyInterface.class));
}
}
ExecutorStrategyInterface类无变化
e.运行程序控制台输出:
到此三种方式介绍结束。
三、总结
第一种方式调用方只需要提供执行器的名称,还可以通过别名灵活配置,但是要实现策略工厂。
第二种通过java反射最为简单,不需要策略工厂类,但是参数需要提供类名及方法名,需要设置两个参数。
第三种方式与第一种类似,只是多了Id的映射,适合的场景与第一种方式不同。
个人建议使用第一种,具体看大家的使用了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。