利用策略模式消除分支
在实际的开发过程中,我们经常会遇到对于不同的对象采用不同的算法或者策略的场景。
一个真实的例子是这样的:
假设现在要将一个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 }
策略模式的优点是可以动态地改变对应的算法,并且在算法扩展时,可以不修改原有代码,而扩展出新的算法(即符合开闭原则)。同时可以有效地减少多条分支的判断,增加代码可读性。
缺点是会产生比较多的策略类,并且客户端需要知道都有哪些策略。