理解策略模式

Posted by AceKei on May 6, 2019

是什么

策略模式属于行为型模式。定义了一系列的算法,并将每一个算法单独封装起来,使每个算法之间可以相互替换,并且算法本身和使用算法的客户端是分割开来的。

优缺点

着重的不是如何实现算法,而是系统如何根据客户端的情况来选择调用哪种算法,从而使程序结构更加灵活,扩展性更强。

优点

  1. 通过抽象、封装来定义一系列的算法,使得这些算法之间可以相互代替,所以为这些算法定义一个抽象类或者接口,来约束算法功能的实现。如果这些算法有公共的功能,还可以在抽象类中实现这些公共的功能。
  2. 实现的这一系列算法之间是可以相互替换的,是平等的,写在一起就是 if …. else ….. 代码块,如果算法里面又有条件语句,就构成了多重语句,造成代码复杂,阅读困难。则可以使用策略模式,将算法分离出来。
  3. 扩展性高。在策略模式中,当有新的算法的时候,不需要在 if … else … 代码块中新增,而是新增一个新的策略实现类,然后告诉客户端该策略的声明,就可以了。

缺点

  1. 客户端必须知道所有策略模式定义出来的算法,并且知道每个策略实现的功能。
  2. 增加了对象的数量。每新增一个算法,就单独封装出一个新的策略实现类,如果策略多的话,那么对象也就多了。
  3. 只适合扁平的算法结构。由于每个策略之间的关系是平等的,可以互相替换的,所以需要调用几个算法的集合时,就比较困难。

怎么用

问题重现

现在有一个图像识别的算法,图像识别的算法又细分为身份证识别,营业执照识别。

按照平时的习惯,客户端传递type过来说明要调用哪种算法,服务端根据type来判断使用哪种算法。

一般代码实现:

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
    public String identifyRecognitionImage(String data) {
        JSONObject obj = JSONObject.parseObject(data);
        if (!obj.containsKey("type")) {
            throw new Exception("缺少参数type");
        }
        //将图片转换成base64,图像识别需要
        String base64 = AliImageUtils.getImageStrFromPath(obj.getString("image_path"));
        if (StringUtil.isEmpty(base64)) {
            throw new Exception("图片base64失败");
        }
        String type = obj.getString("type");
        obj.put("base64", base64);
        JSONObject reJson;
        if ("business".equals(type)) {
            //营业执照识别
            reJson = identifyBusinessRecognitionImage(obj.toJSONString());
        } else if ("card".equals(type)) {
            //身份证识别
            reJson = identifyCardRecognitionImage(obj.toJSONString());
        } else {
            throw new Exception("未知type类型");
        }
        return new Result("识别成功", "data", reJson).toString();
    }

注:data是一个 json 格式的字符串,其中包含了 type-算法类型image_path-图片路径

代码中使用了 if … else … 代码模块,这样看起来似乎是没有什么大问题,但是扩展性差。例如当我们需要新增一个新的 算法-驾照识别 的时候,就需要修改 if … else … ,每次这样做比较麻烦。

如果使用策略模式呢?

服务端接收type

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    public String identifyRecognitionImage(String data) {
        JSONObject obj = JSONObject.parseObject(data);
        if (!obj.containsKey("type")) {
            throw new Exception("缺少参数type");
        }
        //将图片转换成base64,图像识别需要
        String base64 = AliImageUtils.getImageStrFromPath(obj.getString("image_path"));
        if (StringUtil.isEmpty(base64)) {
            throw new Exception("图片base64失败");
        }
        String type = obj.getString("type");
        obj.put("base64", base64);
        //根据type类型获取相应的实现类
        IImageRecognition imageRecognition = AliImageUtils.get(type);
        JSONObject reJson = imageRecognition.recognize(obj.toJSONString());
        return new Result("识别成功", "data", reJson).toString();
    }

定义算法的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
 * @author sk
 * @time 2019/5/15
 * @desc 图像识别,后续新增图像识别的时候,只需在请求的data中指定类型type,实现IImageRecognition接口并在type()方法中声明type即可
 * 参考策略模式
 **/
public interface IImageRecognition {

    /**
     * 图像识别
     *
     * @param data
     * @return
     */
    JSONObject recognize(String data);

    /**
     * 定义类型type
     * @return type
     */
    String type();

}

按需实现接口

营业执照识别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
 * @author sk
 * @time 2019/5/15
 * @desc 营业执照图像识别
 **/
@Service
public class BusinessImageRecognition implements IImageRecognition {
    @Override
    public JSONObject recognize(String data) {
        //具体识别细节
        return identifyBusinessRecognitionImage(data);
    }

    @Override
    public String type() {
        //定义类型
        return "business";
    }
}
身份证识别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
 * @author sk
 * @time 2019/5/15
 * @desc 身份证图像识别
 **/
@Service
public class CardImageRecognition implements IImageRecognition {

    @Override
    public JSONObject recognize(String data) {
        //具体识别细节
        return identifyCardRecognitionImage(data);
    }

    @Override
    public String type() {
        //定义类型
        return "card";
    }
}

工具类:该类主要是将算法实现注入到bean中,然后根据type获取相应的实现类,方便调用

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
/**
 * @author sk
 * @time 2019/5/15
 * @desc 图像识别工具类
 **/
@Component
public class AliImageUtils implements ApplicationContextAware {

    //用来存储具体算法的实现bean
    private static final Map<String, IImageRecognition> MAP = new HashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext ctx)
            throws BeansException {
        //获取实现了IImageRecognition接口的所有实现类
        for (Map.Entry<String, IImageRecognition> entry : ctx.getBeansOfType(IImageRecognition.class).entrySet()) {
            MAP.put(entry.getValue().type(), entry.getValue());
        }
    }

    public static IImageRecognition get(String type) {
                IImageRecognition imageRecognition = MAP.get(type);
        if (null == imageRecognition) {
            throw new Exception("未知type类型");
        }
        return imageRecognition;
    }
}

虽然是暂时加多了代码量,但是后续每当新增一个新的识别算法时,只需要将该新算法实现 IImageRecognition接口 ,并声明 type ,最后将 type 告知客户端即可。

UML

UML类图

image

UML时序图

image

最后

  1. 策略模式体现了开闭原则:策略模式把一系列的可变算法进行封装,从而定义了良好的程序结构,在出现新的算法的时候,可以很容易的将新的算法实现加入到已有的系统中,而已有的实现不需要修改。
  2. 策略模式体现了里氏替换原则:策略模式是一个扁平的结构,各个策略实现都是兄弟关系,实现了同一个接口或者继承了同一个抽象类。这样只要使用策略的客户端保持面向抽象编程,就可以动态的切换不同的策略实现以进行替换。

参考:https://www.cnblogs.com/lewis0077/p/5133812.html