利用策略模式消除分支

Posted Kingsley

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用策略模式消除分支相关的知识,希望对你有一定的参考价值。

在实际的开发过程中,我们经常会遇到对于不同的对象采用不同的算法或者策略的场景。

一个真实的例子是这样的:

假设现在要将一个Student对象存入数据库。在逻辑层,需要对对象的字段进行合法性判断,比如ID是否超过某个阈值,名字长度是否超长。

 1 public class Student {
 2     
 3     private Integer id;
 4     
 5     private String name;
 6     
 7     public Integer getId() {
 8         return id;
 9     }
10 
11     public void setId(Integer id) {
12         this.id = id;
13     }
14 
15     public String getFirstName() {
16         return name;
17     }
18 
19     public void setFirstName(String firstName) {
20         this.name = firstName;
21     }
22 }

1. if else分支方法

最常见的做法,是将参数的类型进行分类。提供一个方法,对传入的参数,根据不同的参数类型进行校验。

package com.huawei.khlin.strategy;

public class App {

    public static void validateField(Object param, FieldType type)
            throws Exception {
        if (type == FieldType.INTEGER) {
            // do something....
        } else if (type == FieldType.DOUBLE) {
            // do something....
        } else if (type == FieldType.CHAR) {
            // do something....
        } else if (type == FieldType.STRING) {
            // do something...
        }
    }

    public static enum FieldType {
        INTEGER, DOUBLE, CHAR, STRING;
    }
    
    public static void main(String[] args) throws Exception {
        Student student = new Student();
        student.setId(Integer.valueOf(1));
        student.setName("kingsley");
        
        validateField(student.getId(), FieldType.INTEGER);
        validateField(student.getName(), FieldType.STRING);
        
        //dao层操作
    }
}

上面的if else分支换成switch效果是一样的。

可以预见的是,当字段的类型越来越多(包括同样类型但阈值不同,例如String 32长度和64长度),validateField方法将会越来越臃肿,分支数量分分钟超过最大圈复杂度15.

2. 策略模式拯救烂代码

策略模式属于对象的行为模式。其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。

个人理解,策略模式实际上就是利用了类的多态性,通过不同的实现类,在不同的场景下使用不同的算法处理事务。

那么,什么时候就要使用策略模式呢?我认为,当某个算法比较复杂,并且你头脑里第一个闪现的想法是用if else来做分支处理,那么就是使用策略模式的时候。

策略模式的结构图如下:

主要有三个组件

Context:持有Strategy对象

Stragety: 抽象的策略类,提供统一的操作方法

ConcretStrategy:具体的策略类,封装了一些行为或算法

具体到上述的场景中,Context就是调用的客户端,即main方法。而根据不同的类型进行参数校验,则是不同的ConcreteStrategy.

这样,我们的代码就可以改成如下:

抽象的策略类

1 public interface Validator {
2     
3     public void validate(Object value) throws Exception;
4 
5 }

具体的策略类

 1 public class IntegerValidator implements Validator {
 2 
 3     private int maxCount = Integer.MAX_VALUE;
 4 
 5     private int minCount = 0;
 6 
 7     public IntegerValidator() {
 8 
 9     }
10 
11     public IntegerValidator(int minCount, int maxCount) {
12         this.minCount = minCount;
13         this.maxCount = maxCount;
14     }
15 
16     @Override
17     public void validate(Object value) throws Exception {
18 
19         int realValue = 0;
20 
21         if (null == value) {
22 
23             realValue = 0;
24 
25         } else {
26             if (!(value instanceof Integer)) {
27                 throw new Exception("field is NOT String Type.");
28             }
29 
30             Integer valueInteger = Integer.valueOf(value.toString());
31 
32             realValue = valueInteger.intValue();
33 
34         }
35 
36         if (!(realValue >= minCount && realValue <= maxCount)) {
37             throw new Exception("integer value out of range.");
38         }
39     }
40 
41 }
 1 public class StringValidator implements Validator {
 2     
 3     private int maxCount = 1024;
 4 
 5     private int minCount = 0;
 6 
 7     public StringValidator() {
 8 
 9     }
10 
11     public StringValidator(int minCount, int maxCount) {
12         this.minCount = minCount;
13         this.maxCount = maxCount;
14     }
15 
16     @Override
17     public void validate(Object value) throws Exception {
18         int charNumber = 0;
19 
20         if (null == value) {
21             
22             charNumber = 0;
23             
24         } else {
25             if (!(value instanceof String)) {
26                 throw new Exception("field is NOT String Type.");
27             }
28 
29             String valueStr = (String) value;
30 
31             charNumber = valueStr.length();
32 
33         }
34         
35         if(!(charNumber >= minCount && charNumber <= maxCount)) {
36             throw new Exception("value out of range.");
37         }
38 
39     }
40 }

Context环境

 1 public class ValidatorContext {
 2     
 3     private Validator validator;
 4     
 5     public ValidatorContext(Validator validator) {
 6         this.validator = validator;
 7     }
 8     
 9     public void operate(Object param) throws Exception {
10         validator.validate(param);
11     }
12 }

main方法调用

 1 public class App {
 2 
 3     private static final Validator DEFAULT_STRING_VALIDATOR = new StringValidator(0, 1024);
 4     
 5     private static final Validator DEFAULT_INTEGER_VALIDATOR = new IntegerValidator(0, 100);
 6     
 7     public static void main(String[] args) throws Exception {
 8         Student student = new Student();
 9         student.setId(Integer.valueOf(1));
10         student.setName("kingsley");
11         
12         ValidatorContext idContext = new ValidatorContext(DEFAULT_INTEGER_VALIDATOR);
13         ValidatorContext nameContext = new ValidatorContext(DEFAULT_STRING_VALIDATOR);
14         
15         idContext.validate(student.getId());
16         nameContext.validate(student.getName());
17         // dao层操作
18     }
19 }

策略模式的优点是可以动态地改变对应的算法,并且在算法扩展时,可以不修改原有代码,而扩展出新的算法(即符合开闭原则)。同时可以有效地减少多条分支的判断,增加代码可读性。

缺点是会产生比较多的策略类,并且客户端需要知道都有哪些策略。

 

以上是关于利用策略模式消除分支的主要内容,如果未能解决你的问题,请参考以下文章

使用责任链模式消除if分支实践

策略模式+工厂方法消除if...else

在Spring boot项目中使用策略模式消除if-else

策略模式重构switch/case分支代码

Git 分支管理 分支管理策略 不使用Fast forward模式进行合并

20180618_Git分支管理策略, 不使用Fast forward模式.