****测试demo git仓库地址:https://github.com/alwaysInRoad/test-strategy-demo2.git
该测试demo为普通项目,导入build path一下lib目录下的jar包,然后运行测试类即可在控制台看到测试结果
说明:
本篇博客是建立在实际项目开发。在项目中应用策略模式,分享出来,希望小伙伴们能快速上手策略模式,并能在项目中优化自己的业务。
业务场景:在开发公司框架时,由于该框架省去了实体类,mapper,mapper.xml、部分service类和controller类,都用统一执行sql方法和统一返回结果。让开发人员只专注于写sql。 这样一来,关于参数的校验,校验方式,校验类型,校验结果,都得存于数据库,然后在统一执行sql的controller层,根据sql,及替换sql内的参数值,实现参数校验。
例如:数据库中存储这样一条sql:insert into t_role(role_id,role_name) values(#{id},${role_name})
另一张参数的校验表中:格式可能是这样的。
在项目实际 运行过程中,因为同一在一个地方执行sql。我们是不能预知用户具体操作是什么,所以不像具体的一个表对应一个实体类,一个mapper类,一个service类,一个controller类,可以具体的制定错误信息。所以此处就很好的利用了策略模式+反射。根据 用户行为,反射生成策略对象,从而得到想要的结果。
代码部分:
1、公共校验接口类: ParamValidate.java (每一个具体校验都实现此接口)
/**
* 参数校验接口类
* @author zr
* @param param 校验参数
* @param validateVal 参数值
* @date 2018-9-17
*/
public interface ParamValidate {
//校验方法,
String validate(String param,String validateVal);
}
2、具体校验类: 例如校验邮箱的:IsEmail.java
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 邮箱校验
* @author zr
* @date 2018-9-17
*
*/
public class IsEmail implements ParamValidate{
@Override
public String validate(String param,String validateVal) {
String str = "^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$";
Pattern p = Pattern.compile(str);
Matcher m = p.matcher(param);
if (m.matches() == false) {
return "邮箱格式不正确";
} else {
return null;
}
}
}
还有其它校验类,这里不一 一列举。看截图
3、策略类:ParamValidateStrategy.java
/**
* 参数校验策略类
* @author zr
* @date 2018-9-17
*
*/
public class ParamValidateStrategy {
//持有的具体策略的对象
private ParamValidate paramValidate;
/**
* 构造函数,传入一个具体策略对象
* @param dbPathSub 具体策略对象
*/
public ParamValidateStrategy(ParamValidate paramValidate) {
super();
this.paramValidate = paramValidate;
}
/**
* 策略方法
*/
public String validate(String param,String validateVal){
return paramValidate.validate(param,validateVal);
}
}
4、在具体的 业务类 业务处理部分,就是策略选择部分。
我是把具体业务抽离出来写了个工具类 ParamValidateUtil .java
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.collections.IteratorUtils;
import com.common.check.ParamValidate;
import com.common.check.ParamValidateStrategy;
import com.model.TAiSqlParam;
import net.sf.json.JSONObject;
/**
* 校验类
* @author zr
* @date 2018年9月17日
*/
public class ParamValidateUtil {
/**
*
* @author zhurun
* @date 2018年9月17日
* @param @param TAiSqlParamList
* @param @param keyParameter
* @param @return
* @param @throws ClassNotFoundException
* @param @throws InstantiationException
* @param @throws IllegalAccessException
* @throws SecurityException
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalArgumentException
*/
public static List<String> ValidateAllFields(List<TAiSqlParam> TAiSqlParamList,String keyParameter) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
JSONObject json = JSONObject.fromObject(keyParameter);
//获得json数组的所有key
Iterator keys = json.keys();
List<String> keylist = IteratorUtils.toList(keys);
//错误信息集合
List<String> errorMsgList = new ArrayList<>();
for (TAiSqlParam tAiSqlParam : TAiSqlParamList) {
if(keylist.contains(tAiSqlParam.getKey())){
String paramValue = json.getString(tAiSqlParam.getKey());
//TODO: 此处待完善
//反射获取枚举类
Class<Enum> clazz = (Class<Enum>)Class.forName("com.common.Constant$ParamValidate");
//获取所有枚举实例
Enum[] enumConstants = clazz.getEnumConstants();
//根据方法名获取方法
//根据方法名获取方法
Method getDesc = clazz.getMethod("getDesc");
Method getIndex = clazz.getMethod("getIndex");
String validateType = null;
for (Enum enum1 : enumConstants) {
//判断枚举值与数据库中存的检查类型是否一致,一致就校验,不一直跳过
if(getIndex.invoke(enum1).equals(tAiSqlParam.getValidateType())){
//执行枚举方法获得枚举实例对应的值
validateType = (String)getDesc.invoke(enum1);
break;
}else{
continue;
}
}
if(validateType==null){
return "校验类型不存在!";
}
//根据数据库中储存的枚举类型,根据枚举值获取对应的枚举描述,从而反射生成校验类(校验类的类名与枚举描述必须一致)
Class<?> forName = Class.forName("com.common.check."+validateType);
Object obj = forName.newInstance();
ParamValidateStrategy paramValidateStrategy = new ParamValidateStrategy((ParamValidate) obj);
String message = paramValidateStrategy.validate(paramValue,tAiSqlParam.getValidateVal());
errorMsgList.add(message);
}
}
return errorMsgList;
}
}
调用工具类的地方:
//获取参数key的字符串值,需要转换成json对象(需要替换sql中的数据)
String keyParameter = request.getParameter(KEY_PARAM);
//根据code得到sql Id
String tAiSqlId = tAiSqlMapper.getTAiSqlIdByCode(code);
//根据sql id得到参数验证list
List<TAiSqlParam> TAiSqlParamList = tAiSqlParamMapper.selectEntitySet(tAiSqlId);
//校验参数 keyParameter是前端传过来的
String message = ParamValidateUtil1.ValidateAllFields(TAiSqlParamList,keyParameter);
ParamValidateUtil 类中所用到的枚举类:Constant.java
/**
* 参数校验枚举类
* @author zr
* @date 2018年9月17日
*/
public static enum ParamValidate {
CheckLength("checkLength",0),
IsAccId("IsAccId",1),
IsAge("IsAge",2),
IsEmail("IsEmail",3),
IsIdCard("IsIdCard",4),
IsPhoneNo("IsPhoneNo",5),
IsPostCode("IsPostCode",6),
IsPwd("IsPwd",7);
// 成员变量
private String desc;
private int index;
// 构造方法
private ParamValidate(String name, int index) {
this.desc = name;
this.index = index;
}
// 覆盖方法
@Override
public String toString() {
return this.index + "_" + this.desc;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
}
ParamValidateUtil 类中所用到的参数实体类:SqlParam.java
public class SqlParam {
private String id;
private String key;
private String sqlId;
private String validateType;
private String validateVal;
private String errorMsg;
get和set方法略
}
总结:我个人理解,策略模式其实就是,利用一个中间类:策略类。来连接策略对象与业务类。策略类不知道业务类用什么策略对象,策略类也不知道具体调什么策略对象,策略类只是拥有父接口对应及调用父接口的方法。这就起到了解耦的作用。 当业务类指定具体策略对象,传给策略类,策略类就知道想要调用的策略对象了。
结语:本人所有文章都立志写的简单易懂,戳中问题点。 当然了,简单的同时可能忽略了很多细节与详细,如有不足的地方,还请谅解并指出。