策略模式
# 问题
如何有效的去避免代码中一长串的if else判断或者switch条件判断?针对更多的回答就是合理的去使用设计来规避这个问题。在设计模式中,可以使用工厂模式或者策略模式来处理这类问题
那么工厂模式和策略模式有什么区别呢?
- 工厂模式是属于创建型设计模式,主要用来针对不同类型创建不同的对象,达到解偶类对象。
- 策略模式是属于行为型设计模式,主要是针对不同的策略做出对应行为,达到行为解偶
# 介绍
定义一系列算法,封装每个算法,并使他们可以互换,不同的策略可以让算法独立于使用它们的客户而变化。
# 结构
- 上下文 (Context) 维护指向具体策略的引用, 且仅通过策略接口与该对象进行交流。
- 策略 (Strategy) 接口是所有具体策略的通用接口, 它声明了一个上下文用于执行策略的方法。
- 具体策略 (Concrete Strategies) 实现了上下文所用算法的各种不同变体。
- 当上下文需要运行算法时, 它会在其已连接的策略对象上调用执行方法。 上下文不清楚其所涉及的策略类型与算法的执行方式。
- 客户端 (Client) 会创建一个特定策略对象并将其传递给上下文。 上下文则会提供一个设置器以便客户端在运行时替换相关联的策略。
# 代码介绍
举个例子,汽车大家肯定都不陌生,愿大家早日完成汽车梦,汽车的不同档(concreteStrategy)就好比不同的策略,驾驶者选择几档则汽车按几档的速度前进,整个选择权在驾驶者(context)手中。
public interface GearStrategy {
// 定义策略执行方法
void algorithm(String param);
}
2
3
4
5
首先还是先定义抽象策略
这里是用接口的形式,还有一种方式可以用抽象方法abstract来写也是一样的。具体就看大家自己选择了。
public abstract class GearStrategyAbstract { // 定义策略执行方法 abstract void algorithm(String param); }
1
2
3
4
public class GearStrategyOne implements GearStrategy {
@Override
public void algorithm(String param) {
System.out.println("当前档位" + param);
}
}
2
3
4
5
6
7
其次定义具体档位策略,实现algorithm方法。
public class Context {
// 缓存所有的策略,当前是无状态的,可以共享策略类对象
private static final Map<String, GearStrategy> strategies = new HashMap<>();
// 第一种写法
static {
strategies.put("one", new GearStrategyOne());
}
public static GearStrategy getStrategy(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("type should not be empty.");
}
return strategies.get(type);
}
// 第二种写法
public static GearStrategy getStrategySecond(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("type should not be empty.");
}
if (type.equals("one")) {
return new GearStrategyOne();
}
return null;
}
public static void main(String[] args) {
// 测试结果
GearStrategy strategyOne = Context.getStrategy("one");
strategyOne.algorithm("1档");
// 结果:当前档位1档
GearStrategy strategyTwo = Context.getStrategySecond("one");
strategyTwo.algorithm("1档");
// 结果:当前档位1档
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
最后就是实现运行时环境(Context),你可以定义成StrategyFactory,但都是一个意思。
在main方法里面的测试demo,可以看到通过不同的type类型,可以实现不同的策略,这就是策略模式主要思想。
在Context里面定义了两种写法:
- 第一种是维护了一个strategies的Map容器。用这种方式就需要判断每种策略是否可以共享使用,它只是作为算法的实现。
- 第二种是直接通过有状态的类,每次根据类型new一个新的策略类对象。这个就需要根据实际业务场景去做的判断。
# 实际案例
策略模式在框架中也在一个很常见的地方体现出来了,而且大家肯定都有使用过。
那就是JDK中的线程池ThreadPoolExecutor
首先都是类似于这样定义一个线程池,里面实现线程池的异常策略。
这个线程池的异常策略就是用的策略模式的思想。
在源码中有RejectedExecutionHandler这个抽象异常策略接口,同时它也有四种拒绝策略。关系图如下:
这就是在框架中的体现了,根据自己的业务场景,合理的选择线程池的异常策略。
# 优缺点
你可以在运行时切换对象内的算法。
你可以将算法的实现和使用算法的代码隔离开来。
你可以使用组合来代替继承。
开闭原则。 你无需对上下文进行修改就能够引入新的策略。
如果你的算法极少发生改变, 那么没有任何理由引入新的类和接口。 使用该模式只会让程序过于复杂。
客户端必须知晓策略间的不同——它需要选择合适的策略。
许多现代编程语言支持函数类型功能, 允许你在一组匿名函数中实现不同版本的算法。 这样, 你使用这些函数的方式就和使用策略对象时完全相同, 无需借助额外的类和接口来保持代码简洁。
# 代码重构如何使用策略模式
- Spring中需要注入资源重构?
如果是在实现业务逻辑需要注入框架中资源呢?比如通过Spring的
@Autowired
来注入bean。可以这样实现:
- 操作 // 很巧妙
public interface Opt {
int apply(int a, int b);
}
@Component(value = "addOpt")
public class AddOpt implements Opt {
@Autowired
xxxAddResource resource; // 这里通过Spring框架注入了资源
@Override
public int apply(int a, int b) {
return resource.process(a, b);
}
}
@Component(value = "devideOpt")
public class devideOpt implements Opt {
@Autowired
xxxDivResource resource; // 这里通过Spring框架注入了资源
@Override
public int apply(int a, int b) {
return resource.process(a, b);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- 策略
@Component
public class OptStrategyContext{
private Map<String, Opt> strategyMap = new ConcurrentHashMap<>();
@Autowired
public OptStrategyContext(Map<String, TalkService> strategyMap) {
this.strategyMap.clear();
this.strategyMap.putAll(strategyMap);
}
public int apply(String opt, int a, int b) {
return strategyMap.get(opt).apply(a, b);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
上述代码在实现中非常常见。