首先定义一个Strategy
接口来表示一个策略:
public interface Strategy {
String flag();
void process();
}
其中flag
方法返回当前策略的唯一标识,process
则是该策略的具体执行逻辑。
下面是Strategy
接口的两个实现类:
public class StrategyImpl1 implements Strategy {
@Override
public String flag() {
return "s1";
}
@Override
public void process() {
System.out.println("strategy 1");
}
}
public class StrategyImpl2 implements Strategy {
@Override
public String flag() {
return "s2";
}
@Override
public void process() {
System.out.println("strategy 2");
}
}
然后定义一个StrategyRunner
接口用来表示策略的调度器:
public interface StrategyRunner {
void run(String flag);
}
run
方法内部通过判断flag
的值来决定具体执行哪一个策略。
下面是一个简单的StrategyRunner
:
public class StrategyRunnerImpl implements StrategyRunner {
private static final List<Strategy> STRATEGIES = Arrays.asList(new StrategyImpl1(), new StrategyImpl2());
private static final Map<String, Strategy> STRATEGY_MAP;
static {
STRATEGY_MAP = STRATEGIES.stream()
.collect(Collectors.toMap(Strategy::flag, s -> s));
}
@Override
public void run(String flag) {
STRATEGY_MAP.get(flag).process();
}
}
在StrategyRunnerImpl
内部,定义了一个STRATEGIES
列表来保存所有Strategy
实现类的实例,以及一个叫做STRATEGY_MAP
的Map
来保存flag
和Strategy
实例之间的对应关系,static
块中的代码用于从STRATEGIES
列表构造STRATEGY_MAP
。这样,在run
方法中就可以很方便地获取到指定flag
的Strategy
实例。
这个实现虽然简单,但是它有个很大的缺点,想象一下,如果我们想添加新的Strategy
实现类,我们不仅需要添加新的实现类,还要修改STRATEGIES
列表的定义。这样就违反了“对扩展开放,对修改关闭”的原则。
借助于Spring的IOC容器和SpringBoot的自动配置,我们可以以一种更加优雅的方式实现上述策略模式。
首先,我们继续使用StrategyImpl1
和StrategyImpl2
这两个实现类。不过,为了将它们注册进Spring的IOC容器,需要给他们标注上Component
注解:
@Component
public class StrategyImpl1 implements Strategy {
...
}
@Component
public class StrategyImpl2 implements Strategy {
...
}
然后,写一个StrategyConfig
配置类,用于向容器中注册一个StrategyRunner
:
@Configuration
public class StrategyConfig {
@Bean
public StrategyRunner strategyRunner(List<Strategy> strategies) {
Map<String, Strategy> strategyMap = strategies.stream()
.collect(Collectors.toMap(Strategy::flag, s -> s));
return flag -> strategyMap.get(flag).process();
}
}
仔细看strategyRunner
方法的实现,不难发现,其中的逻辑与之前的StrategyRunnerImpl
几乎完全相同,也是根据一个List<Strategy>
来构造一个Map<String, Strategy>
。只不过,这里的strategies
列表不是我们自己构造的,而是通过方法参数传进来的。由于strategyRunner
标注了Bean
注解,因此参数上的List<Strategy>
实际上是在SpringBoot初始化过程中从容器获取的(还记得之前我们把两个Strategy
的实现类注册进了容器吗)。
这样,我们再也无需操心系统中一共有多少个Strategy
实现类,因为SpringBoot的自动配置会帮我们把它们全部收集起来。我们只需编写自己的Strategy
实现类,然后将它注册进容器,并在任何需要的地方注入StrategyRunner
:
@Autowired
private StrategyRunner strategyRunner;
然后直接使用strategyRunner
就行了:
strategyRunner.run("s1");
strategyRunner.run("s2");
控制台输出如下:
strategy 1
strategy 2
也就是说,当我们想添加新的Strategy
实现类时,我们只需添加新的代码,而无需修改任何现有的代码,这样就完美地实现了“对扩展开放,对修改关闭”的目标。