近期在做一个数据导入项目,有一个模版选择,模版中可以选择对导入的字段进行效验,例如非空,长度,格式,类型等,如若放在一个类中的话,大量的臃肿代码免不了,例如“if”这样的,那就运用责任链模式,各做各的判断。看到这篇文章不明白责任链的同学们去翻我的上篇文章。
用责任链设计模式来构造这个项目,每个判断做成一个校验器,将每个校验器做成链子,一个接一个的做。库中有五个校验器,假如用户是选择了两个或者一个校验器的话怎么办呢,责任链可是一条链子完整的啊?是可以保持连接,根据排序选哪个做哪个,没选这个也经过这个,只是不做操作轮给下一个,有点麻烦啊个人感觉,怎么办呢?好办,用户导入了模版传进来之后肯定会带有选择的校验器,将校验器取出来,根据选择的几个校验器创建对应的校验器不就好了,选了两个,那我这个链子就只有两条,下面从头来操作演示一遍。
顶层接口
import java.util.HashMap; /** * @Author: ChenBin * @Date: 2018/4/27/0027 18:53 */ public interface Check { HashMap<String, Object> validate(Request request, Client client); }HashMap是用做返回结果的,Request 是一个对象,用来接收参数,Client是一个操作类,客观莫急,往下看,代码都有。
接口实现类
import java.util.HashMap; /** * @Author: ChenBin * @Date: 2018/4/27/0027 18:58 */ public class NullCheck implements Check { @Override public HashMap<String, Object> validate(Request request, Client client) { HashMap<String, Object> map = new HashMap<>(16); if (request.getRequestStr() != null && !"".equals(request.getRequestStr())) { map = client.validate(request, client); }else { map.put("error", "字段不能为空"); return map; } return map; } }大致说明一下,如果传进来的这个参数不为空的话,好,过了非空这关了,去下一关,不然的话停止走到下一个校验器里去(第一关都没过还想过第二关?想什么呢),在这里我就给大家做一个校验器的操作,另外长度校验器什么的,也就是判断长度,差不多代码的。
传参类
/** * @Author: ChenBin * @Date: 2018/5/2/0002 11:28 */ public class Request { private String requestStr; private String templateId; public String getTemplateId() { return templateId; } public void setTemplateId(String templateId) { this.templateId = templateId; } public String getRequestStr() { return requestStr; } public void setRequestStr(String requestStr) { this.requestStr = requestStr; } }两个属性,一个是传入的参数,一个是要传给我的ID,因为我总要知道是哪个模版,模版里有哪些校验器,我要去根据这个ID去数据库里查
场景操作类
import java.util.ArrayList; import java.util.HashMap; import java.util.List; /** * @Author: ChenBin * @Date: 2018/5/2/0002 10:29 */ public class Client implements Check { /** * 用List集合来存储效验规则 * */ List<Check> checks = new ArrayList<>(); /** * 用于标记规则的引用顺序 * */ int index = 0; /** * 往规则链条中添加规则 * */ public Client addCheck(Check filter) { checks.add(filter); return this; } @Override public HashMap<String, Object> validate(Request request, Client client) { HashMap<String, Object> map = new HashMap<>(16); //index初始化为0,checks.size()为校验器的数量,不会执行return操作 if (index == checks.size()) { map.put("res", "校验结束"); return map; } Check check = checks.get(index); //每添加一个过滤规则,index自增1 index++; //根据索引值获取对应的规律规则对字符串进行处理 map = check.validate(request, client); return map; } }这里都加了注释,我大体说明一下,创建一个List集合,因为List是有排序的,校验器也是需要根据顺序一个接着一个的,这个index就是校验器的顺序,有个addCheck方法,是将实现的几个类添加进来用的,validate是继承下来,但在里面做的操作不同,如果顺序等于添加的校验器数量,就是没校验器了,停止操作,不然继续操作校验器,这里有个checks.get(index)是根据排序我们获取对应顺序的校验器,这次是第一个校验器,下一次进来index+1了,那就是第二个校验器了。
写个类来看下效果
/** * @Author: ChenBin * @Date: 2018/5/2/0002 14:30 */ public class Main { public static void main(String[] args) { //模拟传入的值 String msg = "1"; //实例化传参类 Request request=new Request(); //设置参入的值 request.setRequestStr(msg); //实例化场景操作类 Client client = new Client(); //调用add方法,将需要的校验器放进去,多个的话可以在add后面“.addCheck()”继续添加 client.addCheck(new NullCheck()); //调用 System.out.println(client.validate(request, client)); } }打印结果
{res=校验结束}这是非空的结果,下面看空的结果
{error=字段不能为空}测试没问题,那接下来操作导入时候的如何使用校验器了
由于要根据参数的模版ID来查找数据库,找到在数据库对应的校验器,拿到校验器的className,各位知道,我们不能在测试里一样直接new的,这就写死了,根据ID找到数据库里字段的类完整包路径,来写一个工厂模式生成相应的类吧
工厂类
/** * @Author: ChenBin * @Date: 2018/5/2/0002 9:38 */ public class Factory { public static Check Create(String className){ try { return (Check) Class.forName(className).newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } return null; } }这个就不多说明了,就是反射一个实例化对象返回
我用的是SpringBoot加Mybatis,看下查询数据库的Mybatis中SQL
<select id="findByTools" resultType="com.uhope.data.export.domain.EdTools" parameterType="java.lang.String"> SELECT id AS id, name AS name, type AS type, class_name AS className, create_time AS createTime, creator AS creator, description AS description, sort_order AS sortOrder FROM ed_tools WHERE id = ( SELECT tool_id FROM ed_field_tools WHERE field_id = ( SELECT id FROM ed_template_field WHERE template_id = #{templateId} ) ) </select>用了两层子查询(没办法啊,关联表多着呢)来找到这个校验器在数据库中的信息
Mapper
/** * 多表联查工具表信息 * @param templateId 模版ID * @return */ List<EdTools> findByTools(String templateId);Service接口我就不写了,直接看实现类
public HashMap<String, Object> export( Request request) { List<EdTools> tools = edTemplateMapper.findByTools(request.getTemplateId()); Client client = new Client(); for (EdTools tool : tools) { client.addCheck(Factory.Create(tool.getClassName())); } return client.validate(request, client); }首先根据这个ID查询出当前模版选择了多少个校验器,创建场景实现类,根据查询出来的大小做个循环,我们要取出关键的className字段信息,循环的目的是,不管你选择了几个校验器,我就在这个循环里实例化几个校验器添加到addCheck里,自然就变成一个链子了,而对象哪里来?就根据库里的类路径通过工厂类反射来实例化,这样我们就不用关心其他校验器有没有被用到,我只给你想要的校验器,不管几个全都加到了List里了,会在Client这个类里就操作的,直接调用client.validate(request, client)就跑起来了,是成功还是失败都会返回的,这种方式也便于扩展,你需要别的校验器,实现Check接口就行,再实现对应的方法,其他代码无需修改了,if (index == checks.size())会来决定继续还是结束,根据模版选择的效验器数量循环里client.addCheck(Factory.Create(tool.getClassName()));不担心添加的问题,方便扩展。
