目录

定义

策略模式是一种定义了不同算法的方法。 理论上,这些算法都是在做同一件事情,但是实现方式不同。 以相同的方式调用不同的算法实现,降低了算法实现类和算法使用类之间的耦合。

UML

策略模式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端没有任何影响。