前言
近期在做一个数据导入项目,有一个模版选择,模版中可以选择对导入的字段进行效验,例如非空,长度,格式,类型等,如若放在一个类中的话,大量的臃肿代码免不了,例如“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()));
不担心添加的问题,方便扩展。