目录
定义
策略模式是一种定义了不同算法的方法。 理论上,这些算法都是在做同一件事情,但是实现方式不同。 以相同的方式调用不同的算法实现,降低了算法实现类和算法使用类之间的耦合。
UML
代码框架
算法接口
/**
* 算法接口(可以用抽象类,也可以用接口形式)
*/
public abstract class AbsStrategy {
public abstract void algorithm();
}
不同的算法实现
public class StrategyA extends AbsStrategy {
@Override
public void algorithm() {
System.out.println("StartegyA algorithm ...");
}
}
public class StrategyB extends AbsStrategy {
@Override
public void algorithm() {
System.out.println("StartegyB algorithm ...");
}
}
public class StrategyC extends AbsStrategy {
@Override
public void algorithm() {
System.out.println("StartegyC algorithm ...");
}
}
上下文关系类,用于联系客户端和算法实现
public class Context {
private AbsStrategy strategy;
/**
* 初始化时,传入具体的策略对象
* @param strategy
*/
public Context(AbsStrategy strategy){
this.strategy = strategy;
}
/**
* 根据具体策略对象,执行具体的策略算法
*/
public void contextAlgorithm(){
strategy.algorithm();
}
}
客户端调用示例
public class StrategyClientDemo {
public static void main(String[] args) {
Context context;
//策略A
context = new Context(new StrategyA());
context.contextAlgorithm();
//策略B
context = new Context(new StrategyB());
context.contextAlgorithm();
//策略C
context = new Context(new StrategyC());
context.contextAlgorithm();
}
}
优缺点
优点
- 降低了客户端代码和策略算法实现代码的耦合,比如,新增一个算法,只需要新增一个算法实现类。之后改动客户端代码即可。 换句话说是封装了变化,新增一个算法,对其他算法没有任何影响。
- 简化了单元测试。 因为每个算法实现是单独的类,和其他类没有关系,所以可以通过自己的单元测试来完成。
缺点
- 没有消除客户端对算法的选择压力。如
StrategyClientDemo
代码里实际上是需要根据具体的条件去选择不同的算法实现的。这样对客户端不是很友好。 - 这块可以通过简单工厂+策略模式的方式来讲选择权放到Context里,降低客户端的选择压力。
案例
场景
站点上需要接反爬的服务,需要一些基础信息,比如ua、imei、cookie等信息。某些信息在端上(PC/M/APP)获取方式是存在差异的。之后把这些信息组装成实体传给反爬服务。
最基础伪代码如下:
public static void main(String[] args) {
AntiRequest antiRequest = this.buildRequest(request, client);
//调用第三方服务,传入反爬数据
res = IAntiService.anti(antiRequest);
//根据res做处理
...
}
private AntiRequest buildRequest(request, client){
AntiRequest antiRequest = new AntiRequest();
if ("pc".equals(client)){
antiRequest.setXXX(xxx);
}else if ("m".equals(client)){
antiRequest.setXXX(xxx);
}else if ("app".equals(client)){
antiRequest.setXXX(xxx);
}
//其他字段
antiRequest.setBBB(bbb);
return antiRequest;
}
这样很简单,也很容易看明白,但是后期维护的时候,就存在一定的风险,比如,如果后期新增一个端,比如微信端,那就需要在buildRequest
里增加一个if分值判断,增加的同时,一方面可能会影响到其他端的实体构建。另外一方面,代码会看起来很复杂。随着端个性化逻辑的增加,代码不可读。
所以本意上是只增加了微信的处理逻辑,但实际上却可能影响到其他端的处理逻辑。就是一行代码引发的血案了。
使用
结合策略模式,可以做下改造。端上的区分在于构建实体的逻辑可能不同,所以可以抽象出来一个构建实体的接口。之后分端去实现即可。
改造之后的伪代码如下:
算法抽象:
public abstract class AbsAntiBuildStrategy {
public abstract AntiRequest build(String request);
}
具体算法实现,分端实现:
public class MAntiBuildStrategy extends AbsAntiBuildStrategy {
@Override
public AntiRequest build(String request) {
System.out.println("M build");
//构建M实体
return null;
}
}
public class PCAntiBuildStrategy extends AbsAntiBuildStrategy {
@Override
public AntiRequest build(String request) {
System.out.println("PC build");
//构建PC实体
return null;
}
}
Context上下文关系处理
public class AntiContext {
private AbsAntiBuildStrategy strategy;
/**
* 初始化时,传入具体的策略对象
* @param strategy
*/
public AntiContext(AbsAntiBuildStrategy strategy){
this.strategy = strategy;
}
/**
* 根据具体策略对象,执行具体的策略算法
*/
public AntiRequest contextAlgorithm(String request){
return strategy.build(request);
}
}
客户端调用如下:
public static void main(String[] args) {
//客户端类型
String client = "";
//请求参数,从这里解析各个参数
String request = "";
AntiContext antiContext = null;
if ("m".equals(client)){
antiContext = new AntiContext(new MAntiBuildStrategy());
}else if ("pc".equals(client)){
antiContext = new AntiContext(new PCAntiBuildStrategy());
}
//调用三方服务
AntiRequest antiRequest = antiContext.contextAlgorithm(request);
//调用第三方服务,传入反爬数据
res = IAntiService.anti(antiRequest);
}
代码看起来复杂了。但是后续增加新的端的时候,对M和PC端没有任何影响。