1.定义
策略模式是一种行为设计模式, 它能让你定义一系列算法, 并将每种算法分别放入独立的类中, 以使算法的对象能够相互替换。
2.问题
一天, 你打算为游客们创建一款导游程序。 该程序的核心功能是提供美观的地图, 以帮助用户在任何城市中快速定位。
用户期待的程序新功能是自动路线规划: 他们希望输入地址后就能在地图上看到前往目的地的最快路线。
程序的首个版本只能规划公路路线。 驾车旅行的人们对此非常满意。 但很显然, 并非所有人都会在度假时开车。 因此你在下次更新时添加了规划步行路线的功能。 此后, 你又添加了规划公共交通路线的功能。
而这只是个开始。 不久后, 你又要为骑行者规划路线。 又过了一段时间, 你又要为游览城市中的所有景点规划路线。
尽管从商业角度来看, 这款应用非常成功, 但其技术部分却让你非常头疼: 每次添加新的路线规划算法后, 导游应用中主要类的体积就会增加一倍。 终于在某个时候, 你觉得自己没法继续维护这堆代码了。
无论是修复简单缺陷还是微调街道权重, 对某个算法进行任何修改都会影响整个类, 从而增加在已有正常运行代码中引入错误的风险。
此外, 团队合作将变得低效。 如果你在应用成功发布后招募了团队成员, 他们会抱怨在合并冲突的工作上花费了太多时间。 在实现新功能的过程中, 你的团队需要修改同一个巨大的类, 这样他们所编写的代码相互之间就可能会出现冲突。
3.代码示例
假如现在你有一个业务需求,针对四种不同的判断条件,需要选择执行不同的动作。就好比下面的代码
public static void main(String[] args) {
int i = (int) (Math.random()*4);
System.out.println(i);
if(i == 1){
System.out.println(i*1);
}else if(i == 2){
System.out.println(i+1);
}else if(i == 3){
System.out.println(i-1);
}else if(i == 4){
System.out.println(i*10);
}
}
代码很简单,大家都可以看懂。那么如果我们要将上面的代码改造成为策略模式该怎么进行?
首先,我们依据策略模式的定义,定义中表名策略模式之间是可以进行互换的,那我们首先想到的就是使用接口。首先 我们定义策略模式的接口。代码如下:
public interface IReceiptHandleStrategy {
void handle(int i);
}
然后依据我们上面的代码,分别写出对应的实现算法。
public class OneReceiptHandleStrategy implements IReceiptHandleStrategy{
@Override
public void handle(int i) {
System.out.println(i*1);
}
}
public class TwoReceiptHandleStrategy implements IReceiptHandleStrategy{
@Override
public void handle(int i) {
System.out.println(i + 1);
}
}
public class ThreeReceiptHandleStrategy implements IReceiptHandleStrategy{
@Override
public void handle(int i) {
System.out.println(i - 1);
}
}
public class FourReceiptHandleStrategy implements IReceiptHandleStrategy{
@Override
public void handle(int i) {
System.out.println(i * 10);
}
}
可以看到,我们的代码执行的都比较简单,就是相当于把上面的打印写到每一个实现类中。那么接下来,我们需要写一个容器,去封装下面具体的实现类。这个容器要能起到以下的作用,接受策略模式的接口,调用接口的实现方法。代码如下
public class ReceiptStrategyContext {
private IReceiptHandleStrategy receiptHandleStrategy;
public void setReceiptHandleStrategy(IReceiptHandleStrategy receiptHandleStrategy) {
this.receiptHandleStrategy = receiptHandleStrategy;
}
public void handleReceipt(int i){
if (receiptHandleStrategy != null) {
receiptHandleStrategy.handle(i);
}
}
}
那么这些写完了,我们就要考虑如果依据条件去对应正确的实现类。我们很容易可以想到使用map。那么,我们使用简单工厂模式+map。很容易就可以想到下面的代码:
public class ReceiptHandleStrategyFactory {
private static Map<Integer,IReceiptHandleStrategy> receiptHandleStrategyMap;
static {
receiptHandleStrategyMap = new HashMap<>();
receiptHandleStrategyMap.put(1,new OneReceiptHandleStrategy());
receiptHandleStrategyMap.put(2,new TwoReceiptHandleStrategy());
receiptHandleStrategyMap.put(3,new ThreeReceiptHandleStrategy());
receiptHandleStrategyMap.put(4,new FourReceiptHandleStrategy());
}
private ReceiptHandleStrategyFactory(){
}
public static IReceiptHandleStrategy getReceiptHandleStrategy(int i){
return receiptHandleStrategyMap.get(i);
}
}
可以看到,工厂模式里面,map的key值就是对应的判断条件。然后我们修改我们的业务代码
public class CelueFirst {
public static void main(String[] args) {
int i = (int) (Math.random()*4);
System.out.println(i);
ReceiptStrategyContext receiptStrategyContext = new ReceiptStrategyContext();
receiptStrategyContext.setReceiptHandleStrategy(ReceiptHandleStrategyFactory.getReceiptHandleStrategy(i));
receiptStrategyContext.handleReceipt(i);
/*if(i == 1){
System.out.println(i*1);
}else if(i == 2){
System.out.println(i+1);
}else if(i == 3){
System.out.println(i-1);
}else if(i == 4){
System.out.println(i*10);
}*/
}
}
看到这里大家应该可以看到策略模式的好处了,假如现在我需要再额外添加一个条件,之前的代码是在if-else中再添加一个分支,使用策略模式的话。我们就需要再写一个实现类,然后再工厂方法里添加一下就可以了。就是这么简单,而且符合开闭原则,如果需要新增,我们就不需要再去动业务的代码。
4.总结
但是有人可能会说了,我觉得一点也不简单,写那么多的类和接口,不麻烦吗?而且我们开发使用的都是Controller-Service-Dao三层 怎么可能新建那么多类?这问题问题的好,说的对。我们实际开发中,是不能使用策略模式替换if-else的 因为不值得。但是这并不代表我们开发中不使用策略模式。其中有一个人说的非常好。
我们只写业务代码确实不用考虑这些。但是如果涉及到一个全新的业务场景,我们在设计的时候,是可以考虑加入设计模式的。