一、策略模式概念:
顾明思议就是存在多种策略时使用的算法,比如数学题的多种解法,游戏通关的多种方法等等
书本概念:策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。
二、实现策略模式
策略模式把对象本身和运算规则区分开来,因此我们整个模式也分为三个部分。
环境类(Context):用来统一调控策略模式。
抽象策略类(Strategy):策略的抽象类,一般定义为接口方便扩展,抽象出策略方法
具体策略类:具体的策略实现,实现Strategy
三、应用
以当下最火的手游王者荣耀为例,背景:作为一名“资深”射手应该时刻知道自己什么时候应该做什么事情,现有一组策略,当游戏时间在2分钟时暴君刷新我们要推线帮打野打暴君,4分钟时防御塔护盾消失要找机会推塔,十分钟时兵线经济增加要抓紧时间补兵发育
首先看下在不使用任何设计模式的时候我们要如何实现
public class Main {
public static void main(String[] args) {
doSomething(2);
System.out.println();
doSomething(4);
System.out.println();
doSomething(10);
}
private static void doSomething(long time) {
if (time == 2) {
System.out.println("暴君刷新了");
System.out.println("推线协助队友打暴君");
System.out.println("-------------");
} else if (time == 4) {
System.out.println("防御塔护盾消失");
System.out.println("推塔游戏,推塔才是硬道理");
System.out.println("-------------");
}else if(time == 10){
System.out.println("十分钟了兵线经济大幅度增加");
System.out.println("开始吃三路线发育");
System.out.println("-------------");
}
}
}
运行结果:
存在问题:
所有逻辑都在doSomething方法中,加入我们后期需要修改每个时间段的逻辑,那么我们就需要把所有时间段的代码逻辑都看一遍,维护难度高,增加了出现问题的概率
使用策略模式优化:
建立抽象策略类
public interface Strategy {
void doOperate();
}
每个时间段的逻辑各自建立具体策略类
打暴君策略类:
public class KillLoong implements Strategy{
@Override
public void doOperate() {
System.out.println("暴君刷新了");
System.out.println("推线协助队友打暴君");
System.out.println("-------------");
}
}
推塔策略类:
public class PushTower implements Strategy{
@Override
public void doOperate() {
System.out.println("防御塔护盾消失");
System.out.println("推塔游戏,推塔才是硬道理");
System.out.println("-------------");
}
}
补兵策略类:
public class KillSoldier implements Strategy{
@Override
public void doOperate() {
System.out.println("十分钟了兵线经济大幅度增加");
System.out.println("开始吃三路线发育");
System.out.println("-------------");
}
}
环境类统一调控
public class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void makingDecisions(){
this.strategy.doOperate();
}
}
测试类
private static void doSomething(long time) {
if (time <= 2) {
new Context(new KillSoldier()).makingDecisions();
} else if (time > 2 && time < 5) {
new Context(new Support()).makingDecisions();
}else if(time > 20){
new Context(new PushTower()).makingDecisions();
}
}
但是上诉的解决方案还是存在问题,比如我们要新增/删除一个时间节点的策略那么我就需要把整个ifelse语句都看一遍,而且还要修改测试类的代码,不符合我们解耦的目的,所以在这基础上我们进行了优化
四、策略模式进阶(去掉if-else)
策略抽象类,具体策略类、环境类不变,新增一个枚举类
public enum StrategyEnum {
C_2("打暴君", new KillLoong()),
C_4("推塔", new PushTower()),
C_10("补兵发育", new KillSoldier());
public String strategyName;
public Strategy strategyClass;
private StrategyEnum(String strategyName,Strategy strategyClass) {
this.strategyName = strategyName;
this.strategyClass = strategyClass;
}
}
测试类:
public class Main {
public static void main(String[] args) {
doSomething(2);
System.out.println();
doSomething(4);
System.out.println();
doSomething(10);
}
private static void doSomething(long time) {
new Context(StrategyEnum.valueOf("C_"+time).strategyClass);
}
}
这个优化方案使用枚举类进行解耦,新增和删除策略时只需新增策略实现类再对枚举类进行修改即可
五、策略模式终极版(反射优化)
上述的解决方案已经是很优雅的了,但是我们每次新增/删除策略都需要修改枚举类,枚举类使用起来毕竟没有那么灵活必须找到一个唯一的值作为枚举值,而且如果是多个判断条件判断或者是范围判断,使用枚举类就很难操作
实现方案:
在抽象策略类中新增一个方法getNodeInfo
public interface Strategy {
void doOperate();
//获取策略节点的信息
Long getNodeInfo();
}
各个实现类中实现该方法,返回值为该类对应的时间节点
public class KillSoldier implements Strategy{
@Override
public void doOperate() {
System.out.println("十分钟了兵线经济大幅度增加");
System.out.println("开始吃三路线发育");
System.out.println("-------------");
}
@Override
public Long getNodeInfo() {
return 10L;
}
}
public class KillLoong implements Strategy{
@Override
public void doOperate() {
System.out.println("暴君刷新了");
System.out.println("推线协助队友打暴君");
System.out.println("-------------");
}
@Override
public Long getNodeInfo() {
return 2L;
}
}
public class PushTower implements Strategy{
@Override
public void doOperate() {
System.out.println("防御塔护盾消失");
System.out.println("推塔游戏,推塔才是硬道理");
System.out.println("-------------");
}
@Override
public Long getNodeInfo() {
return 4L;
}
}
由于涉及扫描路径所以需要引入依赖Reflections
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.10</version>
</dependency>
测试类代码
public class Main {
public static void main(String[] args) {
doSomething(2);
System.out.println();
doSomething(4);
System.out.println();
doSomething(10);
}
private static void doSomething(long time) {
//获取该路径下所有类,写自己类文件夹的路径,一定要引入reflections依赖,否则会抛异常
Reflections reflections = new Reflections("com.ly.test");
//实现了Strategy接口的策略类
Set<Class<? extends Strategy>> classSet = reflections.getSubTypesOf(Strategy.class);
Class<? extends Strategy> strategy = classSet.stream()
.filter((strategyclass) -> {
try {
//反射获取该策略类的获取节点信息方法
Method method = strategyclass.getDeclaredMethod("getNodeInfo", null);
//执行该方法获取最终策略类的对应的时间节点
Long num = (Long) method.invoke(strategyclass.newInstance(), null);
return num == time;
} catch (IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException e) {
return false;
}
})
.map((strategyclass) -> strategyclass)
.findFirst()
.orElseThrow(() -> new IllegalStateException("没有找到具体的策略类"));
//反射执行该类的策略方法
try {
Method sin = strategy.getDeclaredMethod("doOperate", null);
sin.invoke(strategy.newInstance(), null);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
这样的解决方案虽然第一次实现会非常麻烦需要在调用方写大量的反射逻辑,但是后期维护新增/删除/节点只需策略具体实现类操作即可,完全解耦
该方法写的有点麻烦,如果有更好的优化方法可以一起交流下
六、总结
绝大多数简单场景如果可以找到唯一枚举值那就使用第二种策略方法,如果找不到就使用第一种策略方法,如果需要经常的删除、修改策略节点那么就使用第三种