一、策略模式的定义
1.在解释策略模式之前,我们先了解什么是设计模式?
设计模式——设计模式是人们在面对同类型软件工程设计问题所总结出的一些有用的经验。模式不是代码,而是某类问题的通用设计解决方案。
2.为什么需要去深入的了解设计模式?
通常一些新码农有可能第一份工作就是依赖一个成熟的内部(或开源)框架的基础上,通过配置文件及少量代码开发完成开发需求。不了解框架就被框架束缚,感到困惑。即使有心学习当前框架,一味翻看框架代码,感觉比较晦涩难懂。我们为了更快的学习java源码及各种框架,为了提高工作的效率,为了更快的成为一名合格的架构师,在有一定的java基础后,接着我们应该尽快深入了解java设计模式。
3.策略模式的定义
策略模式——其思想是针对一组算法,将每一种算法都封装到具有共同接口的独立的类中,从而是它们可以相互替换。策略模式的最大特点是使得算法可以在不影响客户端的情况下发生变化,从而改变不同的功能。
举例说明一下策略模式:我们就以Java中的TreeSet为例,TreeSet仅仅知道它只是接收一个Comparator这种接口类型,但是具体是哪种实现类,TreeSet并不关心,实现类在真正的传入TreeSet之前,TreeSet本身是不知道的,所以我们可以自己去实现Comparator接口,然后在实现类里面去封装好我们自己的规则(这里的规则你可以当做是算法),比如说我们要实现对一个集合的元素排序,但是到底是要升序排序还是降序排序,这个完全由我们来去控制,我们可以把这种变化的内容封装到自己的实现类中,真正运行的时候才知道具体的实现。
为了更好的说明策略模式,我们抽象出三个角色,如下所示:
抽象策略角色——这个是一个抽象的角色,通常情况下使用接口或者抽象类去实现。对比来说,就是我们Comparator接口。
具体策略角色——包装了具体的算法和行为。对比来说,就是实现了Comparator接口的实现一组实现类。
环境角色——内部会持有一个抽象角色的引用,给客户端调用。对比来说,就是我们的TreeSet类。说明:TreeSet内部一定会有一个策略类的一个成员变量,这样做的目的在于可以当我们在去创建TreeSet对象的时候,可以接收我们向TreeSet类中传递的具体的策略类。
二、策略模式的应用场景
1.容错恢复机制
什么是容错恢复机制?
容错恢复机制——简单点说就是:程序运行的时候,正常情况下应该按照某种方式来做,如果按照某种方式来做发生错误的话,系统并不会崩溃,也不会就此不能继续向下运行了,而是有容忍出错的能力,不但能容忍程序运行出现错误,还提供出现错误后的备用方案,也就是恢复机制,来代替正常执行的功能,使程序继续向下运行。
注意:下文策略模式demo示例中有代码展示。
三、策略模式的类图UML

四、策略模式demo示例
编写代码的步骤:
(1)定义抽象策略角色(为策略对象定义一个公共的接口)
(2)编写具体策略角色(实际上就是实现上面定义的公共接口)
(3)定义环境角色,内部持有一个策略类的引用
案例一,实现加减乘除的功能。
(1)定义抽象策略角色,代码如下所示:
/**
* 定义抽象策略接口
* 类似于Comparator接口
* */
public interface Strategy {
/**
* 定义一个两个数进行计算的方法。
* */
public int calc(int num1 ,int num2);
}
(2)定义具体的策略角色,代码如下所示:
/**
* 具体的加法策略角色
*
* */
public class AddStrategy implements Strategy {
/**
* 实现calc方法,两个数相加。
* */
public int calc(int num1, int num2) {
return num1 + num2;
}
}
/**
* 具体的减法策略角色
*
* */
public class SubStrategy implements Strategy {
/**
* 实现calc方法,两个数减法计算。
* */
public int calc(int num1, int num2) {
return num1 - num2;
}
}
/**
* 具体的乘法策略角色
*
* */
public class MultStrategy implements Strategy {
/**
* 实现calc方法,两个数相乘操作。
* */
public int calc(int num1, int num2) {
return num1 * num2;
}
}
/**
* 具体的除法策略角色
*
* */
public class DivStrategy implements Strategy{
/**
* 实现calc方法,两个数除法操作。
* */
public int calc(int num1, int num2) {
return num1/num2;
}
}
(3)环境角色,代码如下所示:
/**
* 环境角色
* 类似TreeSet类
* */
public class Environment {
//持有策略接口的引用
Strategy strategy;
//类似TreeSet
public Environment(Strategy strategy){
this.strategy = strategy;
}
public int calculate(int num1 , int num2){
return strategy.calc(num1, num2);
}
}
(4)测试类,代码如下所示:
/**
* 测试类
* */
public class TestStrategy {
public static void main(String[] args) {
Environment environment = new Environment(new AddStrategy());
System.out.println("AddStrategy = "+environment.calculate(8, 2));
Environment environment1 = new Environment(new SubStrategy());
System.out.println("SubStrategy = "+environment1.calculate(8, 2));
Environment environment2 = new Environment(new MultStrategy());
System.out.println("MultStrategy = "+environment2.calculate(8, 2));
Environment environment3 = new Environment(new DivStrategy());
System.out.println("DivStrategy = "+environment3.calculate(8, 2));
}
}
(5)输出结果如下所示:
AddStrategy = 10
SubStrategy = 6
MultStrategy = 16
DivStrategy = 4
案例二、打印日志时具有容错恢复的功能。
需求简介
在一个系统中,所有对系统的操作都要有日志记录,而且这个日志还需要有管理界面,这种情况下通常会把日志记录在数据库里面,方便后续的管理,但是在记录日志到数据库的时候,可能会发生错误,比如暂时连不上数据库了,那就先记录在文件里面,然后在合适的时候把文件中的记录再转录到数据库中。
对于这样的功能的设计,就可以采用策略模式,把日志记录到数据库和日志记录到文件当作两种记录日志的策略,然后在运行期间根据需要进行动态的切换。
(1)定义抽象策略角色,即先定义日志策略接口,代码如下所示:
/**
* 日志记录策略的接口
* */
public interface LogStrategy {
/**
* 功能:记录日志
* @param msg 需要记录的日志信息。
* */
public void log(String msg);
}
(2)定义具体的策略角色,即定义日志策略接口的具体实现类,代码如下所示:
/**
* 把日志记录到数据库
* */
public class DbLog implements LogStrategy {
public void log(String msg) {
//制造错误,假设如果日志的长度超过5的长度就出错。
if(msg != null && msg.trim().length() > 5){
int i = 5/0;
}
System.out.println("现在将日志‘"+msg+"'记录到数据库中。");
}
}
/**
* 把日志记录到文件中
* */
public class FileLog implements LogStrategy {
public void log(String msg) {
System.out.println("现在将日志‘"+msg+"'记录到文件中。");
}
}
(3)环境角色,即定义一个持有日志策略接口的上下文,代码如下所示:
/**
* 记录日志的上下文
* */
public class LogContext {
public void log(String msg){
//在上线文里,自行实现对具体策略的选择。
//优先选用策略:记录到数据库。
LogStrategy logStrategy = new DbLog();
try {
logStrategy.log(msg);
} catch (Exception e) {
//出错了就记录到文件中。
logStrategy = new FileLog();
logStrategy.log(msg);
}
}
}
(4)测试类,即定义一个客户端类,代码如下所示:
/**
* 客户端:调用环境角色,即持有日志策略接口的上下文。
* */
public class LogClient {
public static void main(String[] args) {
/**
* 看看现在的客户端,没有了选择具体实现策略算法的工作,变得非常简单,打印不同长度的信息,可以看出不同的效果。
* */
LogContext log = new LogContext();
log.log("记录日志");//小于5个字符,记录在数据中。
log.log("再次记录日志");//大于5个字符,捕获异常,记录到文件中。
}
}
(5)输出结果如下所示:
现在将日志‘记录日志'记录到数据库中。
现在将日志‘再次记录日志'记录到文件中。
五、策略模式的优缺点
1、优点
(1) 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。
(2) 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
(3)使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
2、缺点
(1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
(2) 策略模式造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。
六、快速学习设计模式的方法
1.多翻阅几篇讲解策略模式的博客,抽象出大家都比较认可的定义。
2.各自结合目前所用框架,找出框架中哪些功能块用到策略模式,进而自己可以根据需要使用策略模式开发一个新的功能块嵌入到当前框架中。总之,理论和实践相结合。不要把策略模式当做一个简单的概念是死记硬背。

本文详细介绍了策略模式的定义、应用场景、类图UML、代码示例及优缺点。策略模式允许在不修改客户端代码的情况下,通过替换算法来改变程序行为。文章通过加减乘除和日志记录的案例展示了策略模式的使用,强调了其在容错恢复机制中的价值。





